package org.apache.maven.plugin.javadoc;
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import com.thoughtworks.qdox.JavaDocBuilder;
import com.thoughtworks.qdox.model.AbstractInheritableJavaEntity;
import com.thoughtworks.qdox.model.AbstractJavaEntity;
import com.thoughtworks.qdox.model.Annotation;
import com.thoughtworks.qdox.model.DocletTag;
import com.thoughtworks.qdox.model.JavaClass;
import com.thoughtworks.qdox.model.JavaField;
import com.thoughtworks.qdox.model.JavaMethod;
import com.thoughtworks.qdox.model.JavaParameter;
import com.thoughtworks.qdox.model.Type;
import com.thoughtworks.qdox.model.TypeVariable;
import com.thoughtworks.qdox.parser.ParseException;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.ClassUtils;
import org.apache.maven.artifact.Artifact;
import org.apache.maven.artifact.DependencyResolutionRequiredException;
import org.apache.maven.artifact.repository.ArtifactRepository;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.plugins.annotations.Component;
import org.apache.maven.plugins.annotations.Parameter;
import org.apache.maven.project.MavenProject;
import org.apache.maven.settings.Settings;
import org.apache.maven.shared.invoker.MavenInvocationException;
import org.codehaus.plexus.components.interactivity.InputHandler;
import org.codehaus.plexus.util.FileUtils;
import org.codehaus.plexus.util.IOUtil;
import org.codehaus.plexus.util.ReaderFactory;
import org.codehaus.plexus.util.StringUtils;
import org.codehaus.plexus.util.WriterFactory;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.StringReader;
import java.io.StringWriter;
import java.io.Writer;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Properties;
import java.util.StringTokenizer;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Abstract class to fix Javadoc documentation and tags in source files.
* <br/>
* See <a href="http://docs.oracle.com/javase/7/docs/technotes/tools/windows/javadoc.html#wheretags">Where Tags
* Can Be Used</a>.
*
* @author <a href="mailto:vincent.siveton@gmail.com">Vincent Siveton</a>
* @version $Id$
* @since 2.6
*/
public abstract class AbstractFixJavadocMojo
extends AbstractMojo
{
/**
* The vm line separator
*/
private static final String EOL = System.getProperty( "line.separator" );
/**
* Tag name for @author *
*/
private static final String AUTHOR_TAG = "author";
/**
* Tag name for @version *
*/
private static final String VERSION_TAG = "version";
/**
* Tag name for @since *
*/
private static final String SINCE_TAG = "since";
/**
* Tag name for @param *
*/
private static final String PARAM_TAG = "param";
/**
* Tag name for @return *
*/
private static final String RETURN_TAG = "return";
/**
* Tag name for @throws *
*/
private static final String THROWS_TAG = "throws";
/**
* Tag name for @link *
*/
private static final String LINK_TAG = "link";
/**
* Tag name for {@inheritDoc} *
*/
private static final String INHERITED_TAG = "{@inheritDoc}";
/**
* Start Javadoc String i.e. <code>/**</code> *
*/
private static final String START_JAVADOC = "/**";
/**
* End Javadoc String i.e. <code>*/</code> *
*/
private static final String END_JAVADOC = "*/";
/**
* Javadoc Separator i.e. <code> * </code> *
*/
private static final String SEPARATOR_JAVADOC = " * ";
/**
* Inherited Javadoc i.e. <code>/** {@inheritDoc} */</code> *
*/
private static final String INHERITED_JAVADOC = START_JAVADOC + " " + INHERITED_TAG + " " + END_JAVADOC;
/**
* <code>all</code> parameter used by {@link #fixTags} *
*/
private static final String FIX_TAGS_ALL = "all";
/**
* <code>public</code> parameter used by {@link #level} *
*/
private static final String LEVEL_PUBLIC = "public";
/**
* <code>protected</code> parameter used by {@link #level} *
*/
private static final String LEVEL_PROTECTED = "protected";
/**
* <code>package</code> parameter used by {@link #level} *
*/
private static final String LEVEL_PACKAGE = "package";
/**
* <code>private</code> parameter used by {@link #level} *
*/
private static final String LEVEL_PRIVATE = "private";
/**
* The Clirr Maven plugin groupId <code>org.codehaus.mojo</code> *
*/
private static final String CLIRR_MAVEN_PLUGIN_GROUPID = "org.codehaus.mojo";
/**
* The Clirr Maven plugin artifactId <code>clirr-maven-plugin</code> *
*/
private static final String CLIRR_MAVEN_PLUGIN_ARTIFACTID = "clirr-maven-plugin";
/**
* The latest Clirr Maven plugin version <code>2.2.2</code> *
*/
private static final String CLIRR_MAVEN_PLUGIN_VERSION = "2.2.2";
/**
* The Clirr Maven plugin goal <code>check</code> *
*/
private static final String CLIRR_MAVEN_PLUGIN_GOAL = "check";
/**
* Java Files Pattern.
*/
public static final String JAVA_FILES = "**\\/*.java";
/**
* Default version value.
*/
public static final String DEFAULT_VERSION_VALUE = "\u0024Id: \u0024Id";
// ----------------------------------------------------------------------
// Mojo components
// ----------------------------------------------------------------------
/**
* Input handler, needed for command line handling.
*/
@Component
private InputHandler inputHandler;
// ----------------------------------------------------------------------
// Mojo parameters
// ----------------------------------------------------------------------
/**
* Version to compare the current code against using the
* <a href="http://mojo.codehaus.org/clirr-maven-plugin/">Clirr Maven Plugin</a>.
* <br/>
* See <a href="#defaultSince">defaultSince</a>.
*/
@Parameter ( property = "comparisonVersion", defaultValue = "(,${project.version})" )
private String comparisonVersion;
/**
* Default value for the Javadoc tag <code>@author</code>.
* <br/>
* If not specified, the <code>user.name</code> defined in the System properties will be used.
*/
@Parameter ( property = "defaultAuthor" )
private String defaultAuthor;
/**
* Default value for the Javadoc tag <code>@since</code>.
*/
@Parameter ( property = "defaultSince", defaultValue = "${project.version}" )
private String defaultSince;
/**
* Default value for the Javadoc tag <code>@version</code>.
* <br/>
* By default, it is <code>$Id:$</code>, corresponding to a
* <a href="http://svnbook.red-bean.com/en/1.1/ch07s02.html#svn-ch-7-sect-2.3.4">SVN keyword</a>.
* Refer to your SCM to use an other SCM keyword.
*/
@Parameter ( property = "defaultVersion", defaultValue = DEFAULT_VERSION_VALUE )
private String defaultVersion = "\u0024Id: \u0024"; // can't use default-value="\u0024Id: \u0024"
/**
* The file encoding to use when reading the source files. If the property
* <code>project.build.sourceEncoding</code> is not set, the platform default encoding is used.
*/
@Parameter ( property = "encoding", defaultValue = "${project.build.sourceEncoding}" )
private String encoding;
/**
* Comma separated excludes Java files, i.e. <code>**/*Test.java</code>.
*/
@Parameter ( property = "excludes" )
private String excludes;
/**
* Comma separated tags to fix in classes, interfaces or methods Javadoc comments.
* Possible values are:
* <ul>
* <li>all (fix all Javadoc tags)</li>
* <li>author (fix only @author tag)</li>
* <li>version (fix only @version tag)</li>
* <li>since (fix only @since tag)</li>
* <li>param (fix only @param tag)</li>
* <li>return (fix only @return tag)</li>
* <li>throws (fix only @throws tag)</li>
* <li>link (fix only @link tag)</li>
* </ul>
*/
@Parameter ( property = "fixTags", defaultValue = "all" )
private String fixTags;
/**
* Flag to fix the classes or interfaces Javadoc comments according the <code>level</code>.
*/
@Parameter ( property = "fixClassComment", defaultValue = "true" )
private boolean fixClassComment;
/**
* Flag to fix the fields Javadoc comments according the <code>level</code>.
*/
@Parameter ( property = "fixFieldComment", defaultValue = "true" )
private boolean fixFieldComment;
/**
* Flag to fix the methods Javadoc comments according the <code>level</code>.
*/
@Parameter ( property = "fixMethodComment", defaultValue = "true" )
private boolean fixMethodComment;
/**
* Flag to remove throws tags from unknown classes.
*/
@Parameter ( property = "removeUnknownThrows", defaultValue = "false" )
private boolean removeUnknownThrows;
/**
* Forcing the goal execution i.e. skip warranty messages (not recommended).
*/
@Parameter ( property = "force" )
private boolean force;
/**
* Flag to ignore or not Clirr.
*/
@Parameter ( property = "ignoreClirr", defaultValue = "false" )
protected boolean ignoreClirr;
/**
* Comma separated includes Java files, i.e. <code>**/*Test.java</code>.
* <p/>
* <strong>Note:</strong> default value is {@code **\/*.java}.
*/
@Parameter ( property = "includes", defaultValue = JAVA_FILES )
private String includes;
/**
* Specifies the access level for classes and members to show in the Javadocs.
* Possible values are:
* <ul>
* <li><a href="http://docs.oracle.com/javase/7/docs/technotes/tools/windows/javadoc.html#public">public</a>
* (shows only public classes and members)</li>
* <li><a href="http://docs.oracle.com/javase/7/docs/technotes/tools/windows/javadoc.html#protected">protected</a>
* (shows only public and protected classes and members)</li>
* <li><a href="http://docs.oracle.com/javase/7/docs/technotes/tools/windows/javadoc.html#package">package</a>
* (shows all classes and members not marked private)</li>
* <li><a href="http://docs.oracle.com/javase/7/docs/technotes/tools/windows/javadoc.html#private">private</a>
* (shows all classes and members)</li>
* </ul>
*/
@Parameter ( property = "level", defaultValue = "protected" )
private String level;
/**
* The local repository where the artifacts are located, used by the tests.
*/
@Parameter ( property = "localRepository" )
private ArtifactRepository localRepository;
/**
* Output directory where Java classes will be rewritten.
*/
@Parameter ( property = "outputDirectory", defaultValue = "${project.build.sourceDirectory}" )
private File outputDirectory;
/**
* The Maven Project Object.
*/
@Parameter( defaultValue = "${project}", readonly = true, required = true )
private MavenProject project;
/**
* The current user system settings for use in Maven.
*/
@Parameter( defaultValue = "${settings}", readonly = true, required = true )
private Settings settings;
// ----------------------------------------------------------------------
// Internal fields
// ----------------------------------------------------------------------
/**
* The current project class loader.
*/
private ClassLoader projectClassLoader;
/**
* Split {@link #fixTags} by comma.
*
* @see {@link #init()}
*/
private String[] fixTagsSplitted;
/**
* New classes found by Clirr.
*/
private List<String> clirrNewClasses;
/**
* New Methods in a Class (the key) found by Clirr.
*/
private Map<String, List<String>> clirrNewMethods;
/**
* List of classes where <code>*since</code> is added. Will be used to add or not this tag in the methods.
*/
private List<String> sinceClasses;
/**
* {@inheritDoc}
*/
public void execute()
throws MojoExecutionException, MojoFailureException
{
if ( !fixClassComment && !fixFieldComment && !fixMethodComment )
{
getLog().info( "Specified to NOT fix classes, fields and methods. Nothing to do." );
return;
}
// verify goal params
init();
if ( fixTagsSplitted.length == 0 )
{
getLog().info( "No fix tag specified. Nothing to do." );
return;
}
// add warranty msg
if ( !preCheck() )
{
return;
}
// run clirr
try
{
executeClirr();
}
catch ( MavenInvocationException e )
{
if ( getLog().isDebugEnabled() )
{
getLog().error( "MavenInvocationException: " + e.getMessage(), e );
}
else
{
getLog().error( "MavenInvocationException: " + e.getMessage() );
}
getLog().info( "Clirr is ignored." );
}
// run qdox and process
try
{
JavaClass[] javaClasses = getQdoxClasses();
if ( javaClasses != null )
{
for ( JavaClass javaClass : javaClasses )
{
processFix( javaClass );
}
}
}
catch ( IOException e )
{
throw new MojoExecutionException( "IOException: " + e.getMessage(), e );
}
}
// ----------------------------------------------------------------------
// protected methods
// ----------------------------------------------------------------------
/**
* @param p not null maven project.
* @return the artifact type.
*/
protected String getArtifactType( MavenProject p )
{
return p.getArtifact().getType();
}
/**
* @param p not null maven project.
* @return the list of source paths for the given project.
*/
protected List<String> getProjectSourceRoots( MavenProject p )
{
return ( p.getCompileSourceRoots() == null
? Collections.<String>emptyList()
: new LinkedList<String>( p.getCompileSourceRoots() ) );
}
/**
* @param p not null
* @return the compile classpath elements
* @throws DependencyResolutionRequiredException
* if any
*/
protected List<String> getCompileClasspathElements( MavenProject p )
throws DependencyResolutionRequiredException
{
return ( p.getCompileClasspathElements() == null
? Collections.<String>emptyList()
: new LinkedList<String>( p.getCompileClasspathElements() ) );
}
/**
* @param javaMethod not null
* @return the fully qualify name of javaMethod with signature
*/
protected static String getJavaMethodAsString( JavaMethod javaMethod )
{
return javaMethod.getParentClass().getFullyQualifiedName() + "#" + javaMethod.getCallSignature();
}
// ----------------------------------------------------------------------
// private methods
// ----------------------------------------------------------------------
/**
* Init goal parameters.
*/
private void init()
{
// defaultAuthor
if ( StringUtils.isEmpty( defaultAuthor ) )
{
defaultAuthor = System.getProperty( "user.name" );
}
// defaultSince
int i = defaultSince.indexOf( "-" + Artifact.SNAPSHOT_VERSION );
if ( i != -1 )
{
defaultSince = defaultSince.substring( 0, i );
}
// fixTags
if ( !FIX_TAGS_ALL.equalsIgnoreCase( fixTags.trim() ) )
{
String[] split = StringUtils.split( fixTags, "," );
List<String> filtered = new LinkedList<String>();
for ( String aSplit : split )
{
String s = aSplit.trim();
if ( JavadocUtil.equalsIgnoreCase( s, FIX_TAGS_ALL, AUTHOR_TAG, VERSION_TAG, SINCE_TAG, PARAM_TAG,
THROWS_TAG, LINK_TAG, RETURN_TAG ) )
{
filtered.add( s );
}
else
{
if ( getLog().isWarnEnabled() )
{
getLog().warn( "Unrecognized '" + s + "' for fixTags parameter. Ignored it!" );
}
}
}
fixTags = StringUtils.join( filtered.iterator(), "," );
}
fixTagsSplitted = StringUtils.split( fixTags, "," );
// encoding
if ( StringUtils.isEmpty( encoding ) )
{
if ( getLog().isWarnEnabled() )
{
getLog().warn( "File encoding has not been set, using platform encoding " + ReaderFactory.FILE_ENCODING
+ ", i.e. build is platform dependent!" );
}
encoding = ReaderFactory.FILE_ENCODING;
}
// level
level = level.trim();
if ( !JavadocUtil.equalsIgnoreCase( level, LEVEL_PUBLIC, LEVEL_PROTECTED, LEVEL_PACKAGE, LEVEL_PRIVATE ) )
{
if ( getLog().isWarnEnabled() )
{
getLog().warn( "Unrecognized '" + level + "' for level parameter, using 'protected' level." );
}
level = "protected";
}
}
/**
* @return <code>true</code> if the user wants to proceed, <code>false</code> otherwise.
* @throws MojoExecutionException if any
*/
private boolean preCheck()
throws MojoExecutionException
{
if ( force )
{
return true;
}
if ( outputDirectory != null && !outputDirectory.getAbsolutePath().equals(
getProjectSourceDirectory().getAbsolutePath() ) )
{
return true;
}
if ( !settings.isInteractiveMode() )
{
getLog().error( "Maven is not attempt to interact with the user for input. "
+ "Verify the <interactiveMode/> configuration in your settings." );
return false;
}
getLog().warn( "" );
getLog().warn( " WARRANTY DISCLAIMER" );
getLog().warn( "" );
getLog().warn( "All warranties with regard to this Maven goal are disclaimed!" );
getLog().warn( "The changes will be done directly in the source code." );
getLog().warn( "The Maven Team strongly recommends the use of a SCM software BEFORE executing this goal." );
getLog().warn( "" );
while ( true )
{
getLog().info( "Are you sure to proceed? [Y]es [N]o" );
try
{
String userExpression = inputHandler.readLine();
if ( userExpression == null || JavadocUtil.equalsIgnoreCase( userExpression, "Y", "Yes" ) )
{
getLog().info( "OK, let's proceed..." );
break;
}
if ( JavadocUtil.equalsIgnoreCase( userExpression, "N", "No" ) )
{
getLog().info( "No changes in your sources occur." );
return false;
}
}
catch ( IOException e )
{
throw new MojoExecutionException( "Unable to read from standard input.", e );
}
}
return true;
}
/**
* @return the source dir as File for the given project
*/
private File getProjectSourceDirectory()
{
return new File( project.getBuild().getSourceDirectory() );
}
/**
* Invoke Maven to run clirr-maven-plugin to find API differences.
*
* @throws MavenInvocationException if any
*/
private void executeClirr()
throws MavenInvocationException
{
if ( ignoreClirr )
{
getLog().info( "Clirr is ignored." );
return;
}
String clirrGoal = getFullClirrGoal();
// http://mojo.codehaus.org/clirr-maven-plugin/check-mojo.html
File clirrTextOutputFile =
FileUtils.createTempFile( "clirr", ".txt", new File( project.getBuild().getDirectory() ) );
Properties properties = new Properties();
properties.put( "textOutputFile", clirrTextOutputFile.getAbsolutePath() );
properties.put( "comparisonVersion", comparisonVersion );
properties.put( "failOnError", "false" );
File invokerDir = new File( project.getBuild().getDirectory(), "invoker" );
invokerDir.mkdirs();
File invokerLogFile = FileUtils.createTempFile( "clirr-maven-plugin", ".txt", invokerDir );
new File( project.getBuild().getDirectory(), "invoker-clirr-maven-plugin.txt" );
JavadocUtil.invokeMaven( getLog(), new File( localRepository.getBasedir() ), project.getFile(),
Collections.singletonList( clirrGoal ), properties, invokerLogFile );
try
{
if ( invokerLogFile.exists() )
{
String invokerLogContent =
StringUtils.unifyLineSeparators( FileUtils.fileRead( invokerLogFile, "UTF-8" ) );
// see org.codehaus.mojo.clirr.AbstractClirrMojo#getComparisonArtifact()
final String artifactNotFoundMsg = "Unable to find a previous version of the project in the repository";
if ( invokerLogContent.contains( artifactNotFoundMsg ) )
{
getLog().warn( "No previous artifact has been deployed, Clirr is ignored." );
return;
}
}
}
catch ( IOException e )
{
getLog().debug( "IOException: " + e.getMessage() );
}
try
{
parseClirrTextOutputFile( clirrTextOutputFile );
}
catch ( IOException e )
{
if ( getLog().isDebugEnabled() )
{
getLog().debug( "IOException: " + e.getMessage(), e );
}
getLog().info( "IOException when parsing Clirr output '" + clirrTextOutputFile.getAbsolutePath()
+ "', Clirr is ignored." );
}
}
/**
* @param clirrTextOutputFile not null
* @throws IOException if any
*/
private void parseClirrTextOutputFile( File clirrTextOutputFile )
throws IOException
{
if ( !clirrTextOutputFile.exists() )
{
if ( getLog().isInfoEnabled() )
{
getLog().info(
"No Clirr output file '" + clirrTextOutputFile.getAbsolutePath() + "' exists, Clirr is ignored." );
}
return;
}
if ( getLog().isInfoEnabled() )
{
getLog().info( "Clirr output file was created: " + clirrTextOutputFile.getAbsolutePath() );
}
clirrNewClasses = new LinkedList<String>();
clirrNewMethods = new LinkedHashMap<String, List<String>>();
BufferedReader reader = null;
try
{
reader = new BufferedReader( ReaderFactory.newReader( clirrTextOutputFile, "UTF-8" ) );
for ( String line = reader.readLine(); line != null; line = reader.readLine() )
{
String[] split = StringUtils.split( line, ":" );
if ( split.length != 4 )
{
if ( getLog().isDebugEnabled() )
{
getLog().debug( "Unable to parse the clirr line: " + line );
}
continue;
}
int code;
try
{
code = Integer.parseInt( split[1].trim() );
}
catch ( NumberFormatException e )
{
if ( getLog().isDebugEnabled() )
{
getLog().debug( "Unable to parse the clirr line: " + line );
}
continue;
}
// http://clirr.sourceforge.net/clirr-core/exegesis.html
// 7011 - Method Added
// 7012 - Method Added to Interface
// 8000 - Class Added
List<String> list;
String[] splits2;
// CHECKSTYLE_OFF: MagicNumber
switch ( code )
{
case 7011:
list = clirrNewMethods.get( split[2].trim() );
if ( list == null )
{
list = new ArrayList<String>();
}
splits2 = StringUtils.split( split[3].trim(), "'" );
if ( splits2.length != 3 )
{
continue;
}
list.add( splits2[1].trim() );
clirrNewMethods.put( split[2].trim(), list );
break;
case 7012:
list = clirrNewMethods.get( split[2].trim() );
if ( list == null )
{
list = new ArrayList<String>();
}
splits2 = StringUtils.split( split[3].trim(), "'" );
if ( splits2.length != 3 )
{
continue;
}
list.add( splits2[1].trim() );
clirrNewMethods.put( split[2].trim(), list );
break;
case 8000:
clirrNewClasses.add( split[2].trim() );
break;
default:
break;
}
// CHECKSTYLE_ON: MagicNumber
}
reader.close();
reader = null;
}
finally
{
IOUtils.closeQuietly( reader );
}
if ( clirrNewClasses.isEmpty() && clirrNewMethods.isEmpty() )
{
getLog().info( "Clirr NOT found API differences." );
}
else
{
getLog().info( "Clirr found API differences, i.e. new classes/interfaces or methods." );
}
}
/**
* @param tag not null
* @return <code>true</code> if <code>tag</code> is defined in {@link #fixTags}.
*/
private boolean fixTag( String tag )
{
if ( fixTagsSplitted.length == 1 && fixTagsSplitted[0].equals( FIX_TAGS_ALL ) )
{
return true;
}
for ( String aFixTagsSplitted : fixTagsSplitted )
{
if ( aFixTagsSplitted.trim().equals( tag ) )
{
return true;
}
}
return false;
}
/**
* Calling Qdox to find {@link JavaClass} objects from the Maven project sources.
* Ignore java class if Qdox has parsing errors.
*
* @return an array of {@link JavaClass} found by QDox
* @throws IOException if any
* @throws MojoExecutionException if any
*/
private JavaClass[] getQdoxClasses()
throws IOException, MojoExecutionException
{
if ( "pom".equalsIgnoreCase( project.getPackaging() ) )
{
getLog().warn( "This project has 'pom' packaging, no Java sources is available." );
return null;
}
List<File> javaFiles = new LinkedList<File>();
for ( String sourceRoot : getProjectSourceRoots( project ) )
{
File f = new File( sourceRoot );
if ( f.isDirectory() )
{
javaFiles.addAll( FileUtils.getFiles( f, includes, excludes, true ) );
}
else
{
if ( getLog().isWarnEnabled() )
{
getLog().warn( f + " doesn't exist. Ignored it." );
}
}
}
JavaDocBuilder builder = new JavaDocBuilder();
builder.getClassLibrary().addClassLoader( getProjectClassLoader() );
builder.setEncoding( encoding );
for ( File f : javaFiles )
{
if ( !f.getAbsolutePath().toLowerCase( Locale.ENGLISH ).endsWith( ".java" ) && getLog().isWarnEnabled() )
{
getLog().warn( "'" + f + "' is not a Java file. Ignored it." );
continue;
}
try
{
builder.addSource( f );
}
catch ( ParseException e )
{
if ( getLog().isWarnEnabled() )
{
getLog().warn( "QDOX ParseException: " + e.getMessage() + ". Can't fix it." );
}
}
}
return builder.getClasses();
}
/**
* @return the classLoader for the given project using lazy instantiation.
* @throws MojoExecutionException if any
*/
private ClassLoader getProjectClassLoader()
throws MojoExecutionException
{
if ( projectClassLoader == null )
{
List<String> classPath;
try
{
classPath = getCompileClasspathElements( project );
}
catch ( DependencyResolutionRequiredException e )
{
throw new MojoExecutionException( "DependencyResolutionRequiredException: " + e.getMessage(), e );
}
List<URL> urls = new ArrayList<URL>( classPath.size() );
for ( String filename : classPath )
{
try
{
urls.add( new File( filename ).toURL() );
}
catch ( MalformedURLException e )
{
throw new MojoExecutionException( "MalformedURLException: " + e.getMessage(), e );
}
}
projectClassLoader = new URLClassLoader( urls.toArray( new URL[urls.size()] ), null );
}
return projectClassLoader;
}
/**
* Process the given {@link JavaClass}, ie add missing javadoc tags depending user parameters.
*
* @param javaClass not null
* @throws IOException if any
* @throws MojoExecutionException if any
*/
private void processFix( JavaClass javaClass )
throws IOException, MojoExecutionException
{
// Skipping inner classes
if ( javaClass.isInner() )
{
return;
}
File javaFile = new File( javaClass.getSource().getURL().getFile() );
// the original java content in memory
final String originalContent = StringUtils.unifyLineSeparators( FileUtils.fileRead( javaFile, encoding ) );
if ( getLog().isDebugEnabled() )
{
getLog().debug( "Analyzing " + javaClass.getFullyQualifiedName() );
}
final StringWriter stringWriter = new StringWriter();
BufferedReader reader = null;
boolean changeDetected = false;
try
{
reader = new BufferedReader( new StringReader( originalContent ) );
int lineNumber = 0;
for ( String line = reader.readLine(); line != null; line = reader.readLine() )
{
lineNumber++;
final String indent = autodetectIndentation( line );
// fixing classes
if ( javaClass.getComment() == null && javaClass.getAnnotations() != null
&& javaClass.getAnnotations().length != 0 )
{
if ( lineNumber == javaClass.getAnnotations()[0].getLineNumber() )
{
changeDetected |= fixClassComment( stringWriter, originalContent, javaClass, indent );
takeCareSingleComment( stringWriter, originalContent, javaClass );
}
}
else
{
if ( lineNumber == javaClass.getLineNumber() )
{
changeDetected |= fixClassComment( stringWriter, originalContent, javaClass, indent );
takeCareSingleComment( stringWriter, originalContent, javaClass );
}
}
// fixing fields
if ( javaClass.getFields() != null )
{
for ( JavaField field : javaClass.getFields() )
{
if ( lineNumber == field.getLineNumber() )
{
changeDetected |= fixFieldComment( stringWriter, javaClass, field, indent );
}
}
}
// fixing methods
if ( javaClass.getMethods() != null )
{
for ( JavaMethod method : javaClass.getMethods() )
{
if ( lineNumber == method.getLineNumber() )
{
changeDetected |= fixMethodComment( stringWriter, originalContent, method, indent );
takeCareSingleComment( stringWriter, originalContent, method );
}
}
}
stringWriter.write( line );
stringWriter.write( EOL );
}
reader.close();
reader = null;
}
finally
{
IOUtil.close( reader );
}
if ( changeDetected )
{
if ( getLog().isInfoEnabled() )
{
getLog().info( "Saving changes to " + javaClass.getFullyQualifiedName() );
}
if ( outputDirectory != null && !outputDirectory.getAbsolutePath().equals(
getProjectSourceDirectory().getAbsolutePath() ) )
{
String path = StringUtils.replace( javaFile.getAbsolutePath().replaceAll( "\\\\", "/" ),
project.getBuild().getSourceDirectory().replaceAll( "\\\\", "/" ), "" );
javaFile = new File( outputDirectory, path );
javaFile.getParentFile().mkdirs();
}
writeFile( javaFile, encoding, stringWriter.toString() );
}
else
{
if ( getLog().isDebugEnabled() )
{
getLog().debug( "No changes made to " + javaClass.getFullyQualifiedName() );
}
}
}
/**
* Take care of block or single comments between Javadoc comment and entity declaration ie:
* <br/>
* <code>
* <font color="#808080">1</font> <font color="#ffffff"> </font>
* <font color="#3f5fbf">/**</font><br />
* <font color="#808080">2</font> <font color="#ffffff"> </font>
* <font color="#3f5fbf">* {Javadoc Comment}</font><br />
* <font color="#808080">3</font> <font color="#ffffff"> </font>
* <font color="#3f5fbf">*/</font><br />
* <font color="#808080">4</font> <font color="#ffffff"> </font>
* <font color="#3f7f5f">/*</font><br />
* <font color="#808080">5</font> <font color="#ffffff"> </font>
* <font color="#3f7f5f">* {Block Comment}</font><br />
* <font color="#808080">6</font> <font color="#ffffff"> </font>
* <font color="#3f7f5f">*/</font><br />
* <font color="#808080">7</font> <font color="#ffffff"> </font>
* <font color="#3f7f5f">// {Single comment}</font><br />
* <font color="#808080">8</font> <font color="#ffffff"> </font>
* <font color="#7f0055"><b>public </b></font><font color="#7f0055"><b>void </b></font>
* <font color="#000000">dummyMethod</font><font color="#000000">( </font>
* <font color="#000000">String s </font><font color="#000000">){}</font>
* </code>
*
* @param stringWriter not null
* @param originalContent not null
* @param entity not null
* @param changeDetected
* @return the updated changeDetected flag
* @throws IOException if any
* @see #extractOriginalJavadoc(String, AbstractJavaEntity)
*/
private void takeCareSingleComment( final StringWriter stringWriter, final String originalContent,
final AbstractInheritableJavaEntity entity )
throws IOException
{
if ( entity.getComment() == null )
{
return;
}
String javadocComment = trimRight( extractOriginalJavadoc( originalContent, entity ) );
String extraComment = javadocComment.substring( javadocComment.indexOf( END_JAVADOC ) + END_JAVADOC.length() );
if ( StringUtils.isNotEmpty( extraComment ) )
{
if ( extraComment.contains( EOL ) )
{
stringWriter.write( extraComment.substring( extraComment.indexOf( EOL ) + EOL.length() ) );
}
else
{
stringWriter.write( extraComment );
}
stringWriter.write( EOL );
}
}
/**
* Add/update Javadoc class comment.
*
* @param stringWriter
* @param originalContent
* @param javaClass
* @param indent
* @return {@code true} if the comment is updated, otherwise {@code false}
* @throws MojoExecutionException
* @throws IOException
*/
private boolean fixClassComment( final StringWriter stringWriter, final String originalContent,
final JavaClass javaClass, final String indent )
throws MojoExecutionException, IOException
{
if ( !fixClassComment )
{
return false;
}
if ( !isInLevel( javaClass.getModifiers() ) )
{
return false;
}
// add
if ( javaClass.getComment() == null )
{
addDefaultClassComment( stringWriter, javaClass, indent );
return true;
}
// update
return updateEntityComment( stringWriter, originalContent, javaClass, indent );
}
/**
* @param modifiers list of modifiers (public, private, protected, package)
* @return <code>true</code> if modifier is align with <code>level</code>.
*/
private boolean isInLevel( String[] modifiers )
{
List<String> modifiersAsList = Arrays.asList( modifiers );
if ( LEVEL_PUBLIC.equalsIgnoreCase( level.trim() ) )
{
return modifiersAsList.contains( LEVEL_PUBLIC );
}
if ( LEVEL_PROTECTED.equalsIgnoreCase( level.trim() ) )
{
return ( modifiersAsList.contains( LEVEL_PUBLIC ) || modifiersAsList.contains( LEVEL_PROTECTED ) );
}
if ( LEVEL_PACKAGE.equalsIgnoreCase( level.trim() ) )
{
return !modifiersAsList.contains( LEVEL_PRIVATE );
}
// should be private (shows all classes and members)
return true;
}
/**
* Add a default Javadoc for the given class, i.e.:
* <br/>
* <code>
* <font color="#808080">1</font> <font color="#ffffff"> </font>
* <font color="#3f5fbf">/**</font><br />
* <font color="#808080">2</font> <font color="#ffffff"> </font>
* <font color="#3f5fbf">* {Comment based on the class name}</font><br />
* <font color="#808080">3</font> <font color="#ffffff"> </font>
* <font color="#3f5fbf">*</font><br />
* <font color="#808080">4</font> <font color="#ffffff"> </font>
* <font color="#3f5fbf">* </font><font color="#7f9fbf">@author </font>
* <font color="#3f5fbf">X {added if addMissingAuthor}</font><br />
* <font color="#808080">5</font> <font color="#ffffff"> </font>
* <font color="#3f5fbf">* </font><font color="#7f9fbf">@version </font>
* <font color="#3f5fbf">X {added if addMissingVersion}</font><br />
* <font color="#808080">6</font> <font color="#ffffff"> </font>
* <font color="#3f5fbf">* </font><font color="#7f9fbf">@since </font>
* <font color="#3f5fbf">X {added if addMissingSince and new classes
* from previous version}</font><br />
* <font color="#808080">7</font> <font color="#ffffff"> </font>
* <font color="#3f5fbf">*/</font><br />
* <font color="#808080">8</font> <font color="#7f0055"><b>public class </b></font>
* <font color="#000000">DummyClass </font><font color="#000000">{}</font></code>
* </code>
*
* @param buffer not null
* @param javaClass not null
* @param indent not null
* @see #getDefaultClassJavadocComment(JavaClass)
* @see #appendDefaultAuthorTag(StringBuilder, String)
* @see #appendDefaultSinceTag(StringBuilder, String)
* @see #appendDefaultVersionTag(StringBuilder, String)
*/
private void addDefaultClassComment( final StringWriter stringWriter, final JavaClass javaClass,
final String indent )
{
StringBuilder sb = new StringBuilder();
sb.append( indent ).append( START_JAVADOC );
sb.append( EOL );
sb.append( indent ).append( SEPARATOR_JAVADOC );
sb.append( getDefaultClassJavadocComment( javaClass ) );
sb.append( EOL );
appendSeparator( sb, indent );
appendDefaultAuthorTag( sb, indent );
appendDefaultVersionTag( sb, indent );
if ( fixTag( SINCE_TAG ) )
{
if ( !ignoreClirr )
{
if ( isNewClassFromLastVersion( javaClass ) )
{
appendDefaultSinceTag( sb, indent );
}
}
else
{
appendDefaultSinceTag( sb, indent );
addSinceClasses( javaClass );
}
}
sb.append( indent ).append( " " ).append( END_JAVADOC );
sb.append( EOL );
stringWriter.write( sb.toString() );
}
/**
* Add Javadoc field comment, only for static fields or interface fields.
*
* @param stringWriter not null
* @param javaClass not null
* @param field not null
* @param indent not null
* @return {@code true} if comment was updated, otherwise {@code false}
* @throws IOException if any
*/
private boolean fixFieldComment( final StringWriter stringWriter, final JavaClass javaClass, final JavaField field,
final String indent )
throws IOException
{
if ( !fixFieldComment )
{
return false;
}
if ( !javaClass.isInterface() && ( !isInLevel( field.getModifiers() ) || !field.isStatic() ) )
{
return false;
}
// add
if ( field.getComment() == null )
{
addDefaultFieldComment( stringWriter, field, indent );
return true;
}
// no update
return false;
}
/**
* Add a default Javadoc for the given field, i.e.:
* <br/>
* <code>
* <font color="#808080">1</font> <font color="#ffffff"> </font>
* <font color="#3f5fbf">/** Constant </font><font color="#7f7f9f"><code></font>
* <font color="#3f5fbf">MY_STRING_CONSTANT="value"</font>
* <font color="#7f7f9f"></code> </font><font color="#3f5fbf">*/</font><br />
* <font color="#808080">2</font> <font color="#ffffff"> </font>
* <font color="#7f0055"><b>public static final </b></font>
* <font color="#000000">String MY_STRING_CONSTANT = </font>
* <font color="#2a00ff">"value"</font><font color="#000000">;</font>
* </code>
*
* @param stringWriter not null
* @param field not null
* @param indent not null
* @throws IOException if any
*/
private void addDefaultFieldComment( final StringWriter stringWriter, final JavaField field, final String indent )
throws IOException
{
StringBuilder sb = new StringBuilder();
sb.append( indent ).append( START_JAVADOC ).append( " " );
sb.append( "Constant <code>" ).append( field.getName() );
if ( StringUtils.isNotEmpty( field.getInitializationExpression() ) )
{
String qualifiedName = field.getType().getJavaClass().getFullyQualifiedName();
if ( qualifiedName.equals( Byte.TYPE.toString() ) || qualifiedName.equals( Short.TYPE.toString() )
|| qualifiedName.equals( Integer.TYPE.toString() ) || qualifiedName.equals( Long.TYPE.toString() )
|| qualifiedName.equals( Float.TYPE.toString() ) || qualifiedName.equals( Double.TYPE.toString() )
|| qualifiedName.equals( Boolean.TYPE.toString() ) || qualifiedName.equals(
Character.TYPE.toString() ) )
{
sb.append( "=" );
sb.append( field.getInitializationExpression().trim() );
}
if ( qualifiedName.equals( String.class.getName() ) )
{
StringBuilder value = new StringBuilder();
String[] lines = getLines( field.getInitializationExpression() );
for ( String line : lines )
{
StringTokenizer token = new StringTokenizer( line.trim(), "\"\n\r" );
while ( token.hasMoreTokens() )
{
String s = token.nextToken();
if ( s.trim().equals( "+" ) )
{
continue;
}
if ( s.trim().endsWith( "\\" ) )
{
s += "\"";
}
value.append( s );
}
}
sb.append( "=\"" );
// reduce the size
// CHECKSTYLE_OFF: MagicNumber
if ( value.length() < 40 )
{
sb.append( value.toString() ).append( "\"" );
}
else
{
sb.append( value.toString().substring( 0, 39 ) ).append( "\"{trunked}" );
}
// CHECKSTYLE_ON: MagicNumber
}
}
sb.append( "</code> " ).append( END_JAVADOC );
sb.append( EOL );
stringWriter.write( sb.toString() );
}
/**
* Add/update Javadoc method comment.
*
* @param stringWriter not null
* @param originalContent not null
* @param javaMethod not null
* @param indent not null
* @return {@code true} if comment was updated, otherwise {@code false}
* @throws MojoExecutionException if any
* @throws IOException if any
*/
private boolean fixMethodComment( final StringWriter stringWriter, final String originalContent,
final JavaMethod javaMethod, final String indent )
throws MojoExecutionException, IOException
{
if ( !fixMethodComment )
{
return false;
}
if ( !javaMethod.getParentClass().isInterface() && !isInLevel( javaMethod.getModifiers() ) )
{
return false;
}
// add
if ( javaMethod.getComment() == null )
{
addDefaultMethodComment( stringWriter, javaMethod, indent );
return true;
}
// update
return updateEntityComment( stringWriter, originalContent, javaMethod, indent );
}
/**
* Add in the buffer a default Javadoc for the given class:
* <br/>
* <code>
* <font color="#808080">1</font> <font color="#ffffff"> </font>
* <font color="#3f5fbf">/**</font><br />
* <font color="#808080">2</font> <font color="#ffffff"> </font>
* <font color="#3f5fbf">* {Comment based on the method name}</font><br />
* <font color="#808080">3</font> <font color="#ffffff"> </font>
* <font color="#3f5fbf">*</font><br />
* <font color="#808080">4</font> <font color="#ffffff"> </font>
* <font color="#3f5fbf">* </font><font color="#7f9fbf">@param </font>
* <font color="#3f5fbf">X {added if addMissingParam}</font><br />
* <font color="#808080">5</font> <font color="#ffffff"> </font>
* <font color="#3f5fbf">* </font><font color="#7f9fbf">@return </font>
* <font color="#3f5fbf">X {added if addMissingReturn}</font><br />
* <font color="#808080">6</font> <font color="#ffffff"> </font>
* <font color="#3f5fbf">* </font><font color="#7f9fbf">@throws </font>
* <font color="#3f5fbf">X {added if addMissingThrows}</font><br />
* <font color="#808080">7</font> <font color="#ffffff"> </font>
* <font color="#3f5fbf">* </font><font color="#7f9fbf">@since </font>
* <font color="#3f5fbf">X {added if addMissingSince and new classes
* from previous version}</font><br />
* <font color="#808080">8</font> <font color="#ffffff"> </font>
* <font color="#3f5fbf">*/</font><br />
* <font color="#808080">9</font> <font color="#7f0055"><b>public </b></font>
* <font color="#7f0055"><b>void </b></font><font color="#000000">dummyMethod</font>
* <font color="#000000">( </font><font color="#000000">String s </font>
* <font color="#000000">){}</font>
* </code>
*
* @param buffer not null
* @param javaMethod not null
* @param indent not null
* @throws MojoExecutionException if any
* @see #getDefaultMethodJavadocComment(JavaMethod)
* @see #appendDefaultSinceTag(StringBuilder, String)
*/
private void addDefaultMethodComment( final StringWriter stringWriter, final JavaMethod javaMethod,
final String indent )
throws MojoExecutionException
{
StringBuilder sb = new StringBuilder();
// special case
if ( isInherited( javaMethod ) )
{
sb.append( indent ).append( INHERITED_JAVADOC );
sb.append( EOL );
stringWriter.write( sb.toString() );
return;
}
sb.append( indent ).append( START_JAVADOC );
sb.append( EOL );
sb.append( indent ).append( SEPARATOR_JAVADOC );
sb.append( getDefaultMethodJavadocComment( javaMethod ) );
sb.append( EOL );
boolean separatorAdded = false;
if ( fixTag( PARAM_TAG ) )
{
if ( javaMethod.getParameters() != null )
{
for ( int i = 0; i < javaMethod.getParameters().length; i++ )
{
JavaParameter javaParameter = javaMethod.getParameters()[i];
separatorAdded = appendDefaultParamTag( sb, indent, separatorAdded, javaParameter );
}
}
// is generic?
if ( javaMethod.getTypeParameters() != null )
{
for ( int i = 0; i < javaMethod.getTypeParameters().length; i++ )
{
TypeVariable typeParam = javaMethod.getTypeParameters()[i];
separatorAdded = appendDefaultParamTag( sb, indent, separatorAdded, typeParam );
}
}
}
if ( fixTag( RETURN_TAG ) && javaMethod.getReturns() != null && !javaMethod.getReturns().isVoid() )
{
separatorAdded = appendDefaultReturnTag( sb, indent, separatorAdded, javaMethod );
}
if ( fixTag( THROWS_TAG ) && javaMethod.getExceptions() != null && javaMethod.getExceptions().length > 0 )
{
for ( int i = 0; i < javaMethod.getExceptions().length; i++ )
{
Type exception = javaMethod.getExceptions()[i];
separatorAdded = appendDefaultThrowsTag( sb, indent, separatorAdded, exception );
}
}
if ( fixTag( SINCE_TAG ) && isNewMethodFromLastRevision( javaMethod ) )
{
separatorAdded = appendDefaultSinceTag( sb, indent, separatorAdded );
}
sb.append( indent ).append( " " ).append( END_JAVADOC );
sb.append( EOL );
stringWriter.write( sb.toString() );
}
/**
* @param stringWriter not null
* @param originalContent not null
* @param entity not null
* @param indent not null
* @param changeDetected
* @return the updated changeDetected flag
* @throws MojoExecutionException if any
* @throws IOException if any
*/
private boolean updateEntityComment( final StringWriter stringWriter, final String originalContent,
final AbstractInheritableJavaEntity entity, final String indent )
throws MojoExecutionException, IOException
{
boolean changeDetected = false;
String old = null;
String s = stringWriter.toString();
int i = s.lastIndexOf( START_JAVADOC );
if ( i != -1 )
{
String tmp = s.substring( 0, i );
if ( tmp.lastIndexOf( EOL ) != -1 )
{
tmp = tmp.substring( 0, tmp.lastIndexOf( EOL ) );
}
old = stringWriter.getBuffer().substring( i );
stringWriter.getBuffer().delete( 0, stringWriter.getBuffer().length() );
stringWriter.write( tmp );
stringWriter.write( EOL );
}
else
{
changeDetected = true;
}
updateJavadocComment( stringWriter, originalContent, entity, indent );
if ( changeDetected )
{
return true; // return now if we already know there's a change
}
return !stringWriter.getBuffer().substring( i ).equals( old );
}
/**
* @param stringWriter not null
* @param originalContent not null
* @param entity not null
* @param indent not null
* @throws MojoExecutionException if any
* @throws IOException if any
*/
private void updateJavadocComment( final StringWriter stringWriter, final String originalContent,
final AbstractInheritableJavaEntity entity, final String indent )
throws MojoExecutionException, IOException
{
if ( entity.getComment() == null && ( entity.getTags() == null || entity.getTags().length == 0 ) )
{
return;
}
boolean isJavaMethod = false;
if ( entity instanceof JavaMethod )
{
isJavaMethod = true;
}
StringBuilder sb = new StringBuilder();
// special case for inherited method
if ( isJavaMethod )
{
JavaMethod javaMethod = (JavaMethod) entity;
if ( isInherited( javaMethod ) )
{
// QDOX-154 could be empty
if ( StringUtils.isEmpty( javaMethod.getComment() ) )
{
sb.append( indent ).append( INHERITED_JAVADOC );
sb.append( EOL );
stringWriter.write( sb.toString() );
return;
}
String javadoc = getJavadocComment( originalContent, javaMethod );
// case: /** {@inheritDoc} */ or no tags
if ( hasInheritedTag( javadoc ) && ( javaMethod.getTags() == null
|| javaMethod.getTags().length == 0 ) )
{
sb.append( indent ).append( INHERITED_JAVADOC );
sb.append( EOL );
stringWriter.write( sb.toString() );
return;
}
if ( javadoc.contains( START_JAVADOC ) )
{
javadoc = javadoc.substring( javadoc.indexOf( START_JAVADOC ) + START_JAVADOC.length() );
}
if ( javadoc.contains( END_JAVADOC ) )
{
javadoc = javadoc.substring( 0, javadoc.indexOf( END_JAVADOC ) );
}
sb.append( indent ).append( START_JAVADOC );
sb.append( EOL );
if ( !javadoc.contains( INHERITED_TAG ) )
{
sb.append( indent ).append( SEPARATOR_JAVADOC ).append( INHERITED_TAG );
sb.append( EOL );
appendSeparator( sb, indent );
}
javadoc = removeLastEmptyJavadocLines( javadoc );
javadoc = alignIndentationJavadocLines( javadoc, indent );
sb.append( javadoc );
sb.append( EOL );
if ( javaMethod.getTags() != null )
{
for ( int i = 0; i < javaMethod.getTags().length; i++ )
{
DocletTag docletTag = javaMethod.getTags()[i];
// Voluntary ignore these tags
if ( JavadocUtil.equals( docletTag.getName(), PARAM_TAG, RETURN_TAG, THROWS_TAG ) )
{
continue;
}
String s = getJavadocComment( originalContent, entity, docletTag );
s = removeLastEmptyJavadocLines( s );
s = alignIndentationJavadocLines( s, indent );
sb.append( s );
sb.append( EOL );
}
}
sb.append( indent ).append( " " ).append( END_JAVADOC );
sb.append( EOL );
if ( hasInheritedTag( sb.toString().trim() ) )
{
sb = new StringBuilder();
sb.append( indent ).append( INHERITED_JAVADOC );
sb.append( EOL );
stringWriter.write( sb.toString() );
return;
}
stringWriter.write( sb.toString() );
return;
}
}
sb.append( indent ).append( START_JAVADOC );
sb.append( EOL );
// comment
if ( StringUtils.isNotEmpty( entity.getComment() ) )
{
updateJavadocComment( sb, originalContent, entity, indent );
}
else
{
addDefaultJavadocComment( sb, entity, indent, isJavaMethod );
}
// tags
if ( entity.getTags() != null && entity.getTags().length > 0 )
{
updateJavadocTags( sb, originalContent, entity, indent, isJavaMethod );
}
else
{
addDefaultJavadocTags( sb, entity, indent, isJavaMethod );
}
sb = new StringBuilder( removeLastEmptyJavadocLines( sb.toString() ) ).append( EOL );
sb.append( indent ).append( " " ).append( END_JAVADOC );
sb.append( EOL );
stringWriter.write( sb.toString() );
}
/**
* @param sb not null
* @param originalContent not null
* @param entity not null
* @param indent not null
* @throws IOException if any
*/
private void updateJavadocComment( final StringBuilder sb, final String originalContent,
final AbstractInheritableJavaEntity entity, final String indent )
throws IOException
{
String comment = getJavadocComment( originalContent, entity );
comment = removeLastEmptyJavadocLines( comment );
comment = alignIndentationJavadocLines( comment, indent );
if ( comment.contains( START_JAVADOC ) )
{
comment = comment.substring( comment.indexOf( START_JAVADOC ) + START_JAVADOC.length() );
comment = indent + SEPARATOR_JAVADOC + comment.trim();
}
if ( comment.contains( END_JAVADOC ) )
{
comment = comment.substring( 0, comment.indexOf( END_JAVADOC ) );
}
if ( fixTag( LINK_TAG ) )
{
comment = replaceLinkTags( comment, entity );
}
String[] lines = getLines( comment );
for ( String line : lines )
{
sb.append( indent ).append( " " ).append( line.trim() );
sb.append( EOL );
}
}
private static String replaceLinkTags( String comment, AbstractInheritableJavaEntity entity )
{
StringBuilder resolvedComment = new StringBuilder();
// scan comment for {@link someClassName} and try to resolve this
Matcher linktagMatcher = Pattern.compile( "\\{@link\\s" ).matcher( comment );
int startIndex = 0;
while ( linktagMatcher.find() )
{
int startName = linktagMatcher.end();
resolvedComment.append( comment.substring( startIndex, startName ) );
int endName = comment.indexOf( "}", startName );
if ( endName >= 0 )
{
String name;
String link = comment.substring( startName, endName );
int hashIndex = link.indexOf( '#' );
if ( hashIndex >= 0 )
{
name = link.substring( 0, hashIndex );
}
else
{
name = link;
}
if ( StringUtils.isNotBlank( name ) )
{
String typeName;
if ( entity instanceof JavaClass )
{
typeName = ( (JavaClass) entity ).resolveType( name.trim() );
}
else
{
typeName = entity.getParentClass().resolveType( name.trim() );
}
if ( typeName == null )
{
typeName = name.trim();
}
else
{
typeName = typeName.replaceAll( "\\$", "." );
}
//adjust name for inner classes
resolvedComment.append( typeName );
}
if ( hashIndex >= 0 )
{
resolvedComment.append( link.substring( hashIndex ).trim() );
}
startIndex = endName;
}
else
{
startIndex = startName;
}
}
resolvedComment.append( comment.substring( startIndex ) );
return resolvedComment.toString();
}
/**
* @param sb not null
* @param entity not null
* @param indent not null
* @param isJavaMethod
*/
private void addDefaultJavadocComment( final StringBuilder sb, final AbstractInheritableJavaEntity entity,
final String indent, final boolean isJavaMethod )
{
sb.append( indent ).append( SEPARATOR_JAVADOC );
if ( isJavaMethod )
{
sb.append( getDefaultMethodJavadocComment( (JavaMethod) entity ) );
}
else
{
sb.append( getDefaultClassJavadocComment( (JavaClass) entity ) );
}
sb.append( EOL );
}
/**
* @param sb not null
* @param originalContent not null
* @param entity not null
* @param indent not null
* @param isJavaMethod
* @throws IOException if any
* @throws MojoExecutionException if any
*/
private void updateJavadocTags( final StringBuilder sb, final String originalContent,
final AbstractInheritableJavaEntity entity, final String indent,
final boolean isJavaMethod )
throws IOException, MojoExecutionException
{
appendSeparator( sb, indent );
// parse tags
JavaEntityTags javaEntityTags = parseJavadocTags( originalContent, entity, indent, isJavaMethod );
// update and write tags
updateJavadocTags( sb, entity, isJavaMethod, javaEntityTags );
// add missing tags...
addMissingJavadocTags( sb, entity, indent, isJavaMethod, javaEntityTags );
}
/**
* Parse entity tags
*
* @param originalContent not null
* @param entity not null
* @param indent not null
* @param isJavaMethod
* @return an instance of {@link JavaEntityTags}
* @throws IOException if any
*/
JavaEntityTags parseJavadocTags( final String originalContent, final AbstractInheritableJavaEntity entity,
final String indent, final boolean isJavaMethod )
throws IOException
{
JavaEntityTags javaEntityTags = new JavaEntityTags( entity, isJavaMethod );
for ( int i = 0; i < entity.getTags().length; i++ )
{
DocletTag docletTag = entity.getTags()[i];
String originalJavadocTag = getJavadocComment( originalContent, entity, docletTag );
originalJavadocTag = removeLastEmptyJavadocLines( originalJavadocTag );
originalJavadocTag = alignIndentationJavadocLines( originalJavadocTag, indent );
javaEntityTags.getNamesTags().add( docletTag.getName() );
if ( isJavaMethod )
{
String[] params = docletTag.getParameters();
if ( params.length < 1 )
{
continue;
}
params = fixQdox173( params );
String paramName = params[0];
if ( docletTag.getName().equals( PARAM_TAG ) )
{
javaEntityTags.putJavadocParamTag( paramName, originalJavadocTag );
}
else if ( docletTag.getName().equals( RETURN_TAG ) )
{
javaEntityTags.setJavadocReturnTag( originalJavadocTag );
}
else if ( docletTag.getName().equals( THROWS_TAG ) )
{
javaEntityTags.putJavadocThrowsTag( paramName, originalJavadocTag );
}
else
{
javaEntityTags.getUnknownTags().add( originalJavadocTag );
}
}
else
{
javaEntityTags.getUnknownTags().add( originalJavadocTag );
}
}
return javaEntityTags;
}
/**
* Write tags according javaEntityTags.
*
* @param sb not null
* @param entity not null
* @param isJavaMethod
* @param javaEntityTags not null
*/
private void updateJavadocTags( final StringBuilder sb, final AbstractInheritableJavaEntity entity,
final boolean isJavaMethod, final JavaEntityTags javaEntityTags )
{
for ( int i = 0; i < entity.getTags().length; i++ )
{
DocletTag docletTag = entity.getTags()[i];
if ( isJavaMethod )
{
JavaMethod javaMethod = (JavaMethod) entity;
String[] params = docletTag.getParameters();
if ( params.length < 1 )
{
continue;
}
if ( docletTag.getName().equals( PARAM_TAG ) )
{
writeParamTag( sb, javaMethod, javaEntityTags, params );
}
else if ( docletTag.getName().equals( RETURN_TAG ) )
{
writeReturnTag( sb, javaMethod, javaEntityTags );
}
else if ( docletTag.getName().equals( THROWS_TAG ) )
{
writeThrowsTag( sb, javaMethod, javaEntityTags, params );
}
else
{
// write unknown tags
for ( Iterator<String> it = javaEntityTags.getUnknownTags().iterator(); it.hasNext(); )
{
String originalJavadocTag = it.next();
String simplified = StringUtils.removeDuplicateWhitespace( originalJavadocTag ).trim();
if ( simplified.contains( "@" + docletTag.getName() ) )
{
it.remove();
sb.append( originalJavadocTag );
sb.append( EOL );
}
}
}
}
else
{
for ( Iterator<String> it = javaEntityTags.getUnknownTags().iterator(); it.hasNext(); )
{
String originalJavadocTag = it.next();
String simplified = StringUtils.removeDuplicateWhitespace( originalJavadocTag ).trim();
if ( simplified.contains( "@" + docletTag.getName() ) )
{
it.remove();
sb.append( originalJavadocTag );
sb.append( EOL );
}
}
}
if ( sb.toString().endsWith( EOL ) )
{
sb.delete( sb.toString().lastIndexOf( EOL ), sb.toString().length() );
}
sb.append( EOL );
}
}
private void writeParamTag( final StringBuilder sb, final JavaMethod javaMethod,
final JavaEntityTags javaEntityTags, String[] params )
{
params = fixQdox173( params );
String paramName = params[0];
if ( !fixTag( PARAM_TAG ) )
{
// write original param tag if found
String originalJavadocTag = javaEntityTags.getJavadocParamTag( paramName );
if ( originalJavadocTag != null )
{
sb.append( originalJavadocTag );
}
return;
}
boolean found = false;
JavaParameter javaParam = javaMethod.getParameterByName( paramName );
if ( javaParam == null )
{
// is generic?
TypeVariable[] typeParams = javaMethod.getTypeParameters();
for ( TypeVariable typeParam : typeParams )
{
if ( typeParam.getGenericValue().equals( paramName ) )
{
found = true;
}
}
}
else
{
found = true;
}
if ( !found )
{
if ( getLog().isWarnEnabled() )
{
getLog().warn(
"Fixed unknown param '" + paramName + "' defined in " + getJavaMethodAsString( javaMethod ) );
}
if ( sb.toString().endsWith( EOL ) )
{
sb.delete( sb.toString().lastIndexOf( EOL ), sb.toString().length() );
}
}
else
{
String originalJavadocTag = javaEntityTags.getJavadocParamTag( paramName );
if ( originalJavadocTag != null )
{
sb.append( originalJavadocTag );
String s = "@" + PARAM_TAG + " " + paramName;
if ( StringUtils.removeDuplicateWhitespace( originalJavadocTag ).trim().endsWith( s ) )
{
sb.append( " " );
sb.append( getDefaultJavadocForType( javaParam.getType() ) );
}
}
}
}
private void writeReturnTag( final StringBuilder sb, final JavaMethod javaMethod,
final JavaEntityTags javaEntityTags )
{
String originalJavadocTag = javaEntityTags.getJavadocReturnTag();
if ( originalJavadocTag == null )
{
return;
}
if ( !fixTag( RETURN_TAG ) )
{
// write original param tag if found
sb.append( originalJavadocTag );
return;
}
if ( StringUtils.isNotEmpty( originalJavadocTag ) && javaMethod.getReturns() != null
&& !javaMethod.getReturns().isVoid() )
{
sb.append( originalJavadocTag );
if ( originalJavadocTag.trim().endsWith( "@" + RETURN_TAG ) )
{
sb.append( " " );
sb.append( getDefaultJavadocForType( javaMethod.getReturns() ) );
}
}
}
void writeThrowsTag( final StringBuilder sb, final JavaMethod javaMethod,
final JavaEntityTags javaEntityTags, final String[] params )
{
String exceptionClassName = params[0];
String originalJavadocTag = javaEntityTags.getJavadocThrowsTag( exceptionClassName );
if ( originalJavadocTag == null )
{
return;
}
if ( !fixTag( THROWS_TAG ) )
{
// write original param tag if found
sb.append( originalJavadocTag );
return;
}
if ( javaMethod.getExceptions() != null )
{
for ( int j = 0; j < javaMethod.getExceptions().length; j++ )
{
Type exception = javaMethod.getExceptions()[j];
if ( exception.getValue().endsWith( exceptionClassName ) )
{
originalJavadocTag =
StringUtils.replace( originalJavadocTag, exceptionClassName, exception.getValue() );
if ( StringUtils.removeDuplicateWhitespace( originalJavadocTag ).trim().endsWith(
"@" + THROWS_TAG + " " + exception.getValue() ) )
{
originalJavadocTag += " if any.";
}
sb.append( originalJavadocTag );
// added qualified name
javaEntityTags.putJavadocThrowsTag( exception.getValue(), originalJavadocTag );
return;
}
}
}
Class<?> clazz = getClass( javaMethod.getParentClass(), exceptionClassName );
if ( clazz != null )
{
if ( ClassUtils.isAssignable( clazz, RuntimeException.class ) )
{
sb.append( StringUtils.replace( originalJavadocTag, exceptionClassName, clazz.getName() ) );
// added qualified name
javaEntityTags.putJavadocThrowsTag( clazz.getName(), originalJavadocTag );
}
else if ( ClassUtils.isAssignable( clazz, Throwable.class ) )
{
getLog().debug( "Removing '" + originalJavadocTag + "'; Throwable not specified by "
+ getJavaMethodAsString( javaMethod ) + " and it is not a RuntimeException." );
}
else
{
getLog().debug( "Removing '" + originalJavadocTag + "'; It is not a Throwable" );
}
}
else if ( removeUnknownThrows )
{
getLog().warn( "Ignoring unknown throws '" + exceptionClassName + "' defined on "
+ getJavaMethodAsString( javaMethod ) );
}
else
{
getLog().warn( "Found unknown throws '" + exceptionClassName + "' defined on "
+ getJavaMethodAsString( javaMethod ) );
sb.append( originalJavadocTag );
if ( params.length == 1 )
{
sb.append( " if any." );
}
javaEntityTags.putJavadocThrowsTag( exceptionClassName, originalJavadocTag );
}
}
/**
* Add missing tags not already written.
*
* @param sb not null
* @param entity not null
* @param indent not null
* @param isJavaMethod
* @param javaEntityTags not null
* @throws MojoExecutionException if any
*/
private void addMissingJavadocTags( final StringBuilder sb, final AbstractInheritableJavaEntity entity,
final String indent, final boolean isJavaMethod,
final JavaEntityTags javaEntityTags )
throws MojoExecutionException
{
if ( isJavaMethod )
{
JavaMethod javaMethod = (JavaMethod) entity;
if ( fixTag( PARAM_TAG ) )
{
if ( javaMethod.getParameters() != null )
{
for ( int i = 0; i < javaMethod.getParameters().length; i++ )
{
JavaParameter javaParameter = javaMethod.getParameters()[i];
if ( javaEntityTags.getJavadocParamTag( javaParameter.getName(), true ) == null )
{
appendDefaultParamTag( sb, indent, javaParameter );
}
}
}
// is generic?
if ( javaMethod.getTypeParameters() != null )
{
for ( int i = 0; i < javaMethod.getTypeParameters().length; i++ )
{
TypeVariable typeParam = javaMethod.getTypeParameters()[i];
if ( javaEntityTags.getJavadocParamTag( "<" + typeParam.getName() + ">", true ) == null )
{
appendDefaultParamTag( sb, indent, typeParam );
}
}
}
}
if ( fixTag( RETURN_TAG ) && StringUtils.isEmpty( javaEntityTags.getJavadocReturnTag() )
&& javaMethod.getReturns() != null && !javaMethod.getReturns().isVoid() )
{
appendDefaultReturnTag( sb, indent, javaMethod );
}
if ( fixTag( THROWS_TAG ) && javaMethod.getExceptions() != null )
{
for ( int i = 0; i < javaMethod.getExceptions().length; i++ )
{
Type exception = javaMethod.getExceptions()[i];
if ( javaEntityTags.getJavadocThrowsTag( exception.getValue(), true ) == null )
{
appendDefaultThrowsTag( sb, indent, exception );
}
}
}
}
else
{
if ( !javaEntityTags.getNamesTags().contains( AUTHOR_TAG ) )
{
appendDefaultAuthorTag( sb, indent );
}
if ( !javaEntityTags.getNamesTags().contains( VERSION_TAG ) )
{
appendDefaultVersionTag( sb, indent );
}
}
if ( fixTag( SINCE_TAG ) && !javaEntityTags.getNamesTags().contains( SINCE_TAG ) )
{
if ( !isJavaMethod )
{
if ( !ignoreClirr )
{
if ( isNewClassFromLastVersion( (JavaClass) entity ) )
{
appendDefaultSinceTag( sb, indent );
}
}
else
{
appendDefaultSinceTag( sb, indent );
addSinceClasses( (JavaClass) entity );
}
}
else
{
if ( !ignoreClirr )
{
if ( isNewMethodFromLastRevision( (JavaMethod) entity ) )
{
appendDefaultSinceTag( sb, indent );
}
}
else
{
if ( sinceClasses != null && !sinceClassesContains( entity.getParentClass() ) )
{
appendDefaultSinceTag( sb, indent );
}
}
}
}
}
/**
* @param sb not null
* @param entity not null
* @param indent not null
* @param isJavaMethod
* @throws MojoExecutionException if any
*/
private void addDefaultJavadocTags( final StringBuilder sb, final AbstractInheritableJavaEntity entity,
final String indent, final boolean isJavaMethod )
throws MojoExecutionException
{
boolean separatorAdded = false;
if ( isJavaMethod )
{
JavaMethod javaMethod = (JavaMethod) entity;
if ( fixTag( PARAM_TAG ) && javaMethod.getParameters() != null )
{
for ( int i = 0; i < javaMethod.getParameters().length; i++ )
{
JavaParameter javaParameter = javaMethod.getParameters()[i];
separatorAdded = appendDefaultParamTag( sb, indent, separatorAdded, javaParameter );
}
}
if ( fixTag( RETURN_TAG ) )
{
if ( javaMethod.getReturns() != null && !javaMethod.getReturns().isVoid() )
{
separatorAdded = appendDefaultReturnTag( sb, indent, separatorAdded, javaMethod );
}
}
if ( fixTag( THROWS_TAG ) && javaMethod.getExceptions() != null )
{
for ( int i = 0; i < javaMethod.getExceptions().length; i++ )
{
Type exception = javaMethod.getExceptions()[i];
separatorAdded = appendDefaultThrowsTag( sb, indent, separatorAdded, exception );
}
}
}
else
{
separatorAdded = appendDefaultAuthorTag( sb, indent, separatorAdded );
separatorAdded = appendDefaultVersionTag( sb, indent, separatorAdded );
}
if ( fixTag( SINCE_TAG ) )
{
if ( !isJavaMethod )
{
JavaClass javaClass = (JavaClass) entity;
if ( !ignoreClirr )
{
if ( isNewClassFromLastVersion( javaClass ) )
{
separatorAdded = appendDefaultSinceTag( sb, indent, separatorAdded );
}
}
else
{
separatorAdded = appendDefaultSinceTag( sb, indent, separatorAdded );
addSinceClasses( javaClass );
}
}
else
{
JavaMethod javaMethod = (JavaMethod) entity;
if ( !ignoreClirr )
{
if ( isNewMethodFromLastRevision( javaMethod ) )
{
separatorAdded = appendDefaultSinceTag( sb, indent, separatorAdded );
}
}
else
{
if ( sinceClasses != null && !sinceClassesContains( javaMethod.getParentClass() ) )
{
separatorAdded = appendDefaultSinceTag( sb, indent, separatorAdded );
}
}
}
}
}
/**
* @param sb not null
* @param indent not null
* @param separatorAdded
* @return true if separator has been added.
*/
private boolean appendDefaultAuthorTag( final StringBuilder sb, final String indent, boolean separatorAdded )
{
if ( !fixTag( AUTHOR_TAG ) )
{
return separatorAdded;
}
if ( !separatorAdded )
{
appendSeparator( sb, indent );
separatorAdded = true;
}
appendDefaultAuthorTag( sb, indent );
return separatorAdded;
}
/**
* @param sb not null
* @param indent not null
*/
private void appendDefaultAuthorTag( final StringBuilder sb, final String indent )
{
if ( !fixTag( AUTHOR_TAG ) )
{
return;
}
sb.append( indent ).append( " * @" ).append( AUTHOR_TAG ).append( " " );
sb.append( defaultAuthor );
sb.append( EOL );
}
/**
* @param sb not null
* @param indent not null
* @param separatorAdded
* @return true if separator has been added.
*/
private boolean appendDefaultSinceTag( final StringBuilder sb, final String indent, boolean separatorAdded )
{
if ( !fixTag( SINCE_TAG ) )
{
return separatorAdded;
}
if ( !separatorAdded )
{
appendSeparator( sb, indent );
separatorAdded = true;
}
appendDefaultSinceTag( sb, indent );
return separatorAdded;
}
/**
* @param sb not null
* @param indent not null
*/
private void appendDefaultSinceTag( final StringBuilder sb, final String indent )
{
if ( !fixTag( SINCE_TAG ) )
{
return;
}
sb.append( indent ).append( " * @" ).append( SINCE_TAG ).append( " " );
sb.append( defaultSince );
sb.append( EOL );
}
/**
* @param sb not null
* @param indent not null
* @param separatorAdded
* @return true if separator has been added.
*/
private boolean appendDefaultVersionTag( final StringBuilder sb, final String indent, boolean separatorAdded )
{
if ( !fixTag( VERSION_TAG ) )
{
return separatorAdded;
}
if ( !separatorAdded )
{
appendSeparator( sb, indent );
separatorAdded = true;
}
appendDefaultVersionTag( sb, indent );
return separatorAdded;
}
/**
* @param sb not null
* @param indent not null
*/
private void appendDefaultVersionTag( final StringBuilder sb, final String indent )
{
if ( !fixTag( VERSION_TAG ) )
{
return;
}
sb.append( indent ).append( " * @" ).append( VERSION_TAG ).append( " " );
sb.append( defaultVersion );
sb.append( EOL );
}
/**
* @param sb not null
* @param indent not null
* @param separatorAdded
* @param javaParameter not null
* @return true if separator has been added.
*/
private boolean appendDefaultParamTag( final StringBuilder sb, final String indent, boolean separatorAdded,
final JavaParameter javaParameter )
{
if ( !fixTag( PARAM_TAG ) )
{
return separatorAdded;
}
if ( !separatorAdded )
{
appendSeparator( sb, indent );
separatorAdded = true;
}
appendDefaultParamTag( sb, indent, javaParameter );
return separatorAdded;
}
/**
* @param sb not null
* @param indent not null
* @param separatorAdded
* @param typeParameter not null
* @return true if separator has been added.
*/
private boolean appendDefaultParamTag( final StringBuilder sb, final String indent, boolean separatorAdded,
final TypeVariable typeParameter )
{
if ( !fixTag( PARAM_TAG ) )
{
return separatorAdded;
}
if ( !separatorAdded )
{
appendSeparator( sb, indent );
separatorAdded = true;
}
appendDefaultParamTag( sb, indent, typeParameter );
return separatorAdded;
}
/**
* @param sb not null
* @param indent not null
* @param javaParameter not null
*/
private void appendDefaultParamTag( final StringBuilder sb, final String indent, final JavaParameter javaParameter )
{
if ( !fixTag( PARAM_TAG ) )
{
return;
}
sb.append( indent ).append( " * @" ).append( PARAM_TAG ).append( " " );
sb.append( javaParameter.getName() );
sb.append( " " );
sb.append( getDefaultJavadocForType( javaParameter.getType() ) );
sb.append( EOL );
}
/**
* @param sb not null
* @param indent not null
* @param typeParameter not null
*/
private void appendDefaultParamTag( final StringBuilder sb, final String indent, final TypeVariable typeParameter )
{
if ( !fixTag( PARAM_TAG ) )
{
return;
}
sb.append( indent ).append( " * @" ).append( PARAM_TAG ).append( " " );
sb.append( "<" ).append( typeParameter.getName() ).append( ">" );
sb.append( " " );
sb.append( getDefaultJavadocForType( typeParameter ) );
sb.append( EOL );
}
/**
* @param sb not null
* @param indent not null
* @param separatorAdded
* @param javaMethod not null
* @return true if separator has been added.
*/
private boolean appendDefaultReturnTag( final StringBuilder sb, final String indent, boolean separatorAdded,
final JavaMethod javaMethod )
{
if ( !fixTag( RETURN_TAG ) )
{
return separatorAdded;
}
if ( !separatorAdded )
{
appendSeparator( sb, indent );
separatorAdded = true;
}
appendDefaultReturnTag( sb, indent, javaMethod );
return separatorAdded;
}
/**
* @param sb not null
* @param indent not null
* @param javaMethod not null
*/
private void appendDefaultReturnTag( final StringBuilder sb, final String indent, final JavaMethod javaMethod )
{
if ( !fixTag( RETURN_TAG ) )
{
return;
}
sb.append( indent ).append( " * @" ).append( RETURN_TAG ).append( " " );
sb.append( getDefaultJavadocForType( javaMethod.getReturns() ) );
sb.append( EOL );
}
/**
* @param sb not null
* @param indent not null
* @param separatorAdded
* @param exception not null
* @return true if separator has been added.
*/
private boolean appendDefaultThrowsTag( final StringBuilder sb, final String indent, boolean separatorAdded,
final Type exception )
{
if ( !fixTag( THROWS_TAG ) )
{
return separatorAdded;
}
if ( !separatorAdded )
{
appendSeparator( sb, indent );
separatorAdded = true;
}
appendDefaultThrowsTag( sb, indent, exception );
return separatorAdded;
}
/**
* @param sb not null
* @param indent not null
* @param exception not null
*/
private void appendDefaultThrowsTag( final StringBuilder sb, final String indent, final Type exception )
{
if ( !fixTag( THROWS_TAG ) )
{
return;
}
sb.append( indent ).append( " * @" ).append( THROWS_TAG ).append( " " );
sb.append( exception.getJavaClass().getFullyQualifiedName() );
sb.append( " if any." );
sb.append( EOL );
}
/**
* @param sb not null
* @param indent not null
*/
private void appendSeparator( final StringBuilder sb, final String indent )
{
sb.append( indent ).append( " *" );
sb.append( EOL );
}
/**
* Verify if a method has <code>@java.lang.Override()</code> annotation or if it is an inherited method
* from an interface or a super class. The goal is to handle <code>{@inheritDoc}</code> tag.
*
* @param javaMethod not null
* @return <code>true</code> if the method is inherited, <code>false</code> otherwise.
* @throws MojoExecutionException if any
*/
private boolean isInherited( JavaMethod javaMethod )
throws MojoExecutionException
{
if ( javaMethod.getAnnotations() != null )
{
for ( int i = 0; i < javaMethod.getAnnotations().length; i++ )
{
Annotation annotation = javaMethod.getAnnotations()[i];
if ( annotation.toString().equals( "@java.lang.Override()" ) )
{
return true;
}
}
}
Class<?> clazz = getClass( javaMethod.getParentClass().getFullyQualifiedName() );
List<Class<?>> interfaces = ClassUtils.getAllInterfaces( clazz );
for ( Class<?> intface : interfaces )
{
if ( isInherited( intface, javaMethod ) )
{
return true;
}
}
List<Class<?>> classes = ClassUtils.getAllSuperclasses( clazz );
for ( Class<?> superClass : classes )
{
if ( isInherited( superClass, javaMethod ) )
{
return true;
}
}
return false;
}
/**
* @param clazz the Java class object, not null
* @param javaMethod the QDox JavaMethod object not null
* @return <code>true</code> if <code>javaMethod</code> exists in the given <code>clazz</code>,
* <code>false</code> otherwise.
* @see #isInherited(JavaMethod)
*/
private boolean isInherited( Class<?> clazz, JavaMethod javaMethod )
{
for ( Method method : clazz.getDeclaredMethods() )
{
if ( !method.getName().equals( javaMethod.getName() ) )
{
continue;
}
if ( method.getParameterTypes().length != javaMethod.getParameters().length )
{
continue;
}
boolean found = false;
int j = 0;
for ( Class<?> paramType : method.getParameterTypes() )
{
String name1 = paramType.getName();
String name2 = javaMethod.getParameters()[j++].getType().getFullQualifiedName();
found = name1.equals( name2 ); // TODO check algo, seems broken (only takes in account the last param)
}
return found;
}
return false;
}
/**
* @param type
* @return
*/
private String getDefaultJavadocForType( Type type )
{
StringBuilder sb = new StringBuilder();
if ( !TypeVariable.class.isAssignableFrom( type.getClass() ) && type.isPrimitive() )
{
if ( type.isArray() )
{
sb.append( "an array of " );
}
else
{
sb.append( "a " );
}
return sb.append( type.getJavaClass().getFullyQualifiedName() ).append( "." ).toString();
}
StringBuilder javadocLink = new StringBuilder();
try
{
getClass( type.getJavaClass().getFullyQualifiedName() );
String s = type.getJavaClass().getFullyQualifiedName();
s = StringUtils.replace( s, "$", "." );
javadocLink.append( "{@link " ).append( s ).append( "}" );
}
catch ( Exception e )
{
javadocLink.append( type.getJavaClass().getFullyQualifiedName() );
}
if ( type.isArray() )
{
sb.append( "an array of " ).append( javadocLink.toString() ).append( " objects." );
}
else
{
sb.append( "a " ).append( javadocLink.toString() ).append( " object." );
}
return sb.toString();
}
/**
* Check under Clirr if this given class is newer from the last version.
*
* @param javaClass a given class not null
* @return <code>true</code> if Clirr said that this class is added from the last version,
* <code>false</code> otherwise or if {@link #clirrNewClasses} is null.
*/
private boolean isNewClassFromLastVersion( JavaClass javaClass )
{
return ( clirrNewClasses != null ) && clirrNewClasses.contains( javaClass.getFullyQualifiedName() );
}
/**
* Check under Clirr if this given method is newer from the last version.
*
* @param javaMethod a given method not null
* @return <code>true</code> if Clirr said that this method is added from the last version,
* <code>false</code> otherwise or if {@link #clirrNewMethods} is null.
* @throws MojoExecutionException if any
*/
private boolean isNewMethodFromLastRevision( JavaMethod javaMethod )
throws MojoExecutionException
{
if ( clirrNewMethods == null )
{
return false;
}
List<String> clirrMethods = clirrNewMethods.get( javaMethod.getParentClass().getFullyQualifiedName() );
if ( clirrMethods == null )
{
return false;
}
for ( String clirrMethod : clirrMethods )
{
// see net.sf.clirr.core.internal.checks.MethodSetCheck#getMethodId(JavaType clazz, Method method)
String retrn = "";
if ( javaMethod.getReturns() != null )
{
retrn = javaMethod.getReturns().getFullQualifiedName();
}
StringBuilder params = new StringBuilder();
for ( JavaParameter parameter : javaMethod.getParameters() )
{
if ( params.length() > 0 )
{
params.append( ", " );
}
params.append( parameter.getResolvedValue() );
}
if ( clirrMethod.contains( retrn + " " ) && clirrMethod.contains( javaMethod.getName() + "(" )
&& clirrMethod.contains( "(" + params.toString() + ")" ) )
{
return true;
}
}
return false;
}
/**
* @param className not null
* @return the Class corresponding to the given class name using the project classloader.
* @throws MojoExecutionException if class not found
* @see {@link ClassUtils#getClass(ClassLoader, String, boolean)}
* @see {@link #getProjectClassLoader()}
*/
private Class<?> getClass( String className )
throws MojoExecutionException
{
try
{
return ClassUtils.getClass( getProjectClassLoader(), className, false );
}
catch ( ClassNotFoundException e )
{
throw new MojoExecutionException( "ClassNotFoundException: " + e.getMessage(), e );
}
}
/**
* Returns the Class object assignable for {@link RuntimeException} class and associated with the given
* exception class name.
*
* @param currentClass not null
* @param exceptionClassName not null, an exception class name defined as:
* <ul>
* <li>exception class fully qualified</li>
* <li>exception class in the same package</li>
* <li>exception inner class</li>
* <li>exception class in java.lang package</li>
* </ul>
* @return the class if found, otherwise {@code null}.
* @see #getClass(String)
*/
private Class<?> getClass( JavaClass currentClass, String exceptionClassName )
{
String[] potentialClassNames =
new String[]{ exceptionClassName, currentClass.getPackage().getName() + "." + exceptionClassName,
currentClass.getPackage().getName() + "." + currentClass.getName() + "$" + exceptionClassName,
"java.lang." + exceptionClassName };
Class<?> clazz = null;
for ( String potentialClassName : potentialClassNames )
{
try
{
clazz = getClass( potentialClassName );
}
catch ( MojoExecutionException e )
{
// nop
}
if ( clazz != null )
{
return clazz;
}
}
return null;
}
/**
* @param javaClass not null
*/
private void addSinceClasses( JavaClass javaClass )
{
if ( sinceClasses == null )
{
sinceClasses = new ArrayList<String>();
}
sinceClasses.add( javaClass.getFullyQualifiedName() );
}
private boolean sinceClassesContains( JavaClass javaClass )
{
return sinceClasses.contains( javaClass.getFullyQualifiedName() );
}
// ----------------------------------------------------------------------
// Static methods
// ----------------------------------------------------------------------
/**
* Write content into the given javaFile and using the given encoding.
* All line separators will be unified.
*
* @param javaFile not null
* @param encoding not null
* @param content not null
* @throws IOException if any
*/
private static void writeFile( final File javaFile, final String encoding, final String content )
throws IOException
{
Writer writer = null;
try
{
writer = WriterFactory.newWriter( javaFile, encoding );
writer.write( StringUtils.unifyLineSeparators( content ) );
writer.close();
writer = null;
}
finally
{
IOUtil.close( writer );
}
}
/**
* @return the full clirr goal, i.e. <code>groupId:artifactId:version:goal</code>. The clirr-plugin version
* could be load from the pom.properties in the clirr-maven-plugin dependency.
*/
private static String getFullClirrGoal()
{
StringBuilder sb = new StringBuilder();
sb.append( CLIRR_MAVEN_PLUGIN_GROUPID ).append( ":" ).append( CLIRR_MAVEN_PLUGIN_ARTIFACTID ).append( ":" );
String clirrVersion = CLIRR_MAVEN_PLUGIN_VERSION;
InputStream resourceAsStream = null;
try
{
String resource = "META-INF/maven/" + CLIRR_MAVEN_PLUGIN_GROUPID + "/" + CLIRR_MAVEN_PLUGIN_ARTIFACTID
+ "/pom.properties";
resourceAsStream = AbstractFixJavadocMojo.class.getClassLoader().getResourceAsStream( resource );
if ( resourceAsStream != null )
{
Properties properties = new Properties();
properties.load( resourceAsStream );
resourceAsStream.close();
resourceAsStream = null;
if ( StringUtils.isNotEmpty( properties.getProperty( "version" ) ) )
{
clirrVersion = properties.getProperty( "version" );
}
}
}
catch ( IOException e )
{
// nop
}
finally
{
IOUtil.close( resourceAsStream );
}
sb.append( clirrVersion ).append( ":" ).append( CLIRR_MAVEN_PLUGIN_GOAL );
return sb.toString();
}
/**
* Default comment for class.
*
* @param javaClass not null
* @return a default comment for class.
*/
private static String getDefaultClassJavadocComment( final JavaClass javaClass )
{
StringBuilder sb = new StringBuilder();
sb.append( "<p>" );
if ( Arrays.asList( javaClass.getModifiers() ).contains( "abstract" ) )
{
sb.append( "Abstract " );
}
sb.append( javaClass.getName() );
if ( !javaClass.isInterface() )
{
sb.append( " class." );
}
else
{
sb.append( " interface." );
}
sb.append( "</p>" );
return sb.toString();
}
/**
* Default comment for method with taking care of getter/setter in the javaMethod name.
*
* @param javaMethod not null
* @return a default comment for method.
*/
private static String getDefaultMethodJavadocComment( final JavaMethod javaMethod )
{
if ( javaMethod.isConstructor() )
{
return "<p>Constructor for " + javaMethod.getName() + ".</p>";
}
if ( javaMethod.getName().length() > 3 && ( javaMethod.getName().startsWith( "get" )
|| javaMethod.getName().startsWith( "set" ) ) )
{
String field = StringUtils.lowercaseFirstLetter( javaMethod.getName().substring( 3 ) );
JavaClass clazz = javaMethod.getParentClass();
if ( clazz.getFieldByName( field ) == null )
{
return "<p>" + javaMethod.getName() + ".</p>";
}
StringBuilder sb = new StringBuilder();
sb.append( "<p>" );
if ( javaMethod.getName().startsWith( "get" ) )
{
sb.append( "Getter " );
}
else if ( javaMethod.getName().startsWith( "set" ) )
{
sb.append( "Setter " );
}
sb.append( "for the field <code>" ).append( field ).append( "</code>.</p>" );
return sb.toString();
}
return "<p>" + javaMethod.getName() + ".</p>";
}
/**
* Try to find if a Javadoc comment has an {@link #INHERITED_TAG} for instance:
* <pre>
* /** {@inheritDoc} */
* </pre>
* or
* <pre>
* /**
* * {@inheritDoc}
* */
* </pre>
*
* @param content not null
* @return <code>true</code> if the content has an inherited tag, <code>false</code> otherwise.
*/
private static boolean hasInheritedTag( final String content )
{
final String inheritedTagPattern =
"^\\s*(\\/\\*\\*)?(\\s*(\\*)?)*(\\{)@inheritDoc\\s*(\\})(\\s*(\\*)?)*(\\*\\/)?$";
return Pattern.matches( inheritedTagPattern, StringUtils.removeDuplicateWhitespace( content ) );
}
/**
* Workaround for QDOX-146 about whitespace.
* Ideally we want to use <code>entity.getComment()</code>
* <br/>
* For instance, with the following snippet:
* <br/>
* <p/>
* <code>
* <font color="#808080">1</font> <font color="#ffffff"></font><br />
* <font color="#808080">2</font> <font color="#ffffff"> </font>
* <font color="#3f5fbf">/**</font><br />
* <font color="#808080">3</font> <font color="#ffffff"> </font>
* <font color="#3f5fbf">* Dummy Javadoc comment.</font><br />
* <font color="#808080">4</font> <font color="#ffffff"> </font>
* <font color="#3f5fbf">* </font><font color="#7f9fbf">@param </font>
* <font color="#3f5fbf">s a String</font><br />
* <font color="#808080">5</font> <font color="#ffffff"> </font>
* <font color="#3f5fbf">*/</font><br />
* <font color="#808080">6</font> <font color="#ffffff"> </font>
* <font color="#7f0055"><b>public </b></font><font color="#7f0055"><b>void </b></font>
* <font color="#000000">dummyMethod</font><font color="#000000">( </font>
* <font color="#000000">String s </font><font color="#000000">){}</font><br />
* </code>
* <p/>
* <br/>
* The return will be:
* <br/>
* <p/>
* <code>
* <font color="#808080">1</font> <font color="#ffffff"> </font>
* <font color="#3f5fbf">* Dummy Javadoc comment.</font><br />
* </code>
*
* @param javaClassContent original class content not null
* @param entity not null
* @return the javadoc comment for the entity without any tags.
* @throws IOException if any
*/
private static String getJavadocComment( final String javaClassContent, final AbstractJavaEntity entity )
throws IOException
{
if ( entity.getComment() == null )
{
return "";
}
String originalJavadoc = extractOriginalJavadocContent( javaClassContent, entity );
StringBuilder sb = new StringBuilder();
BufferedReader lr = new BufferedReader( new StringReader( originalJavadoc ) );
String line;
while ( ( line = lr.readLine() ) != null )
{
String l = StringUtils.removeDuplicateWhitespace( line.trim() );
if ( l.startsWith( "* @" ) || l.startsWith( "*@" ) )
{
break;
}
sb.append( line ).append( EOL );
}
return trimRight( sb.toString() );
}
/**
* Work around for QDOX-146 about whitespace.
* Ideally we want to use <code>docletTag.getValue()</code>
* <br/>
* For instance, with the following snippet:
* <br/>
* <p/>
* <code>
* <font color="#808080">1</font> <font color="#ffffff"></font><br />
* <font color="#808080">2</font> <font color="#ffffff"> </font>
* <font color="#3f5fbf">/**</font><br />
* <font color="#808080">3</font> <font color="#ffffff"> </font>
* <font color="#3f5fbf">* Dummy Javadoc comment.</font><br />
* <font color="#808080">4</font> <font color="#ffffff"> </font>
* <font color="#3f5fbf">* </font><font color="#7f9fbf">@param </font>
* <font color="#3f5fbf">s a String</font><br />
* <font color="#808080">5</font> <font color="#ffffff"> </font>
* <font color="#3f5fbf">*/</font><br />
* <font color="#808080">6</font> <font color="#ffffff"> </font>
* <font color="#7f0055"><b>public </b></font><font color="#7f0055"><b>void </b></font>
* <font color="#000000">dummyMethod</font><font color="#000000">( </font>
* <font color="#000000">String s </font><font color="#000000">){}</font><br />
* </code>
* <p/>
* <br/>
* The return will be:
* <br/>
* <p/>
* <code>
* <font color="#808080">1</font> <font color="#ffffff"> </font>
* <font color="#3f5fbf">* </font><font color="#7f9fbf">@param </font>
* <font color="#3f5fbf">s a String</font><br />
* </code>
*
* @param javaClassContent original class content not null
* @param entity not null
* @param docletTag not null
* @return the javadoc comment for the entity without Javadoc tags.
* @throws IOException if any
*/
private String getJavadocComment( final String javaClassContent, final AbstractInheritableJavaEntity entity,
final DocletTag docletTag )
throws IOException
{
if ( docletTag.getValue() == null || docletTag.getParameters().length == 0 )
{
return "";
}
String originalJavadoc = extractOriginalJavadocContent( javaClassContent, entity );
String[] params = fixQdox173( docletTag.getParameters() );
String paramValue = params[0];
StringBuilder sb = new StringBuilder();
BufferedReader lr = new BufferedReader( new StringReader( originalJavadoc ) );
String line;
boolean found = false;
while ( ( line = lr.readLine() ) != null )
{
String l = StringUtils.removeDuplicateWhitespace( line.trim() );
if ( l.startsWith( "* @" + docletTag.getName() + " " + paramValue ) || l.startsWith(
"*@" + docletTag.getName() + " " + paramValue ) )
{
if ( fixTag( LINK_TAG ) )
{
line = replaceLinkTags( line, entity );
}
sb.append( line ).append( EOL );
found = true;
}
else
{
if ( l.startsWith( "* @" ) || l.startsWith( "*@" ) )
{
found = false;
}
if ( found )
{
if ( fixTag( LINK_TAG ) )
{
line = replaceLinkTags( line, entity );
}
sb.append( line ).append( EOL );
}
}
}
return trimRight( sb.toString() );
}
/**
* Extract the original Javadoc and others comments up to {@link #START_JAVADOC} form the entity. This method
* takes care of the Javadoc indentation. All javadoc lines will be trimmed on right.
* <br/>
* For instance, with the following snippet:
* <br/>
* <p/>
* <code>
* <font color="#808080">1</font> <font color="#ffffff"></font><br />
* <font color="#808080">2</font> <font color="#ffffff"> </font>
* <font color="#3f5fbf">/**</font><br />
* <font color="#808080">3</font> <font color="#ffffff"> </font>
* <font color="#3f5fbf">* Dummy Javadoc comment.</font><br />
* <font color="#808080">4</font> <font color="#ffffff"> </font>
* <font color="#3f5fbf">* </font><font color="#7f9fbf">@param </font>
* <font color="#3f5fbf">s a String</font><br />
* <font color="#808080">5</font> <font color="#ffffff"> </font>
* <font color="#3f5fbf">*/</font><br />
* <font color="#808080">6</font> <font color="#ffffff"> </font>
* <font color="#7f0055"><b>public </b></font><font color="#7f0055"><b>void </b></font>
* <font color="#000000">dummyMethod</font><font color="#000000">( </font>
* <font color="#000000">String s </font><font color="#000000">){}</font><br />
* </code>
* <p/>
* <br/>
* The return will be:
* <br/>
* <p/>
* <code>
* <font color="#808080">1</font> <font color="#ffffff"> </font>
* <font color="#3f5fbf">/**</font><br />
* <font color="#808080">2</font> <font color="#ffffff"> </font>
* <font color="#3f5fbf">* Dummy Javadoc comment.</font><br />
* <font color="#808080">3</font> <font color="#ffffff"> </font>
* <font color="#3f5fbf">* </font><font color="#7f9fbf">@param </font>
* <font color="#3f5fbf">s a String</font><br />
* <font color="#808080">4</font> <font color="#ffffff"> </font>
* <font color="#3f5fbf">*/</font><br />
* </code>
*
* @param javaClassContent not null
* @param entity not null
* @return return the original javadoc as String for the current entity
* @throws IOException if any
*/
private static String extractOriginalJavadoc( final String javaClassContent, final AbstractJavaEntity entity )
throws IOException
{
if ( entity.getComment() == null )
{
return "";
}
String[] javaClassContentLines = getLines( javaClassContent );
List<String> list = new LinkedList<String>();
for ( int i = entity.getLineNumber() - 2; i >= 0; i-- )
{
String line = javaClassContentLines[i];
list.add( trimRight( line ) );
if ( line.trim().startsWith( START_JAVADOC ) )
{
break;
}
}
Collections.reverse( list );
return StringUtils.join( list.iterator(), EOL );
}
/**
* Extract the Javadoc comment between {@link #START_JAVADOC} and {@link #END_JAVADOC} form the entity. This method
* takes care of the Javadoc indentation. All javadoc lines will be trimmed on right.
* <br/>
* For instance, with the following snippet:
* <br/>
* <p/>
* <code>
* <font color="#808080">1</font> <font color="#ffffff"></font><br />
* <font color="#808080">2</font> <font color="#ffffff"> </font>
* <font color="#3f5fbf">/**</font><br />
* <font color="#808080">3</font> <font color="#ffffff"> </font>
* <font color="#3f5fbf">* Dummy Javadoc comment.</font><br />
* <font color="#808080">4</font> <font color="#ffffff"> </font>
* <font color="#3f5fbf">* </font><font color="#7f9fbf">@param </font>
* <font color="#3f5fbf">s a String</font><br />
* <font color="#808080">5</font> <font color="#ffffff"> </font>
* <font color="#3f5fbf">*/</font><br />
* <font color="#808080">6</font> <font color="#ffffff"> </font>
* <font color="#7f0055"><b>public </b></font><font color="#7f0055"><b>void </b></font>
* <font color="#000000">dummyMethod</font><font color="#000000">( </font>
* <font color="#000000">String s </font><font color="#000000">){}</font><br />
* </code>
* <p/>
* <br/>
* The return will be:
* <br/>
* <p/>
* <code>
* <font color="#808080">1</font> <font color="#ffffff"> </font>
* <font color="#3f5fbf">* Dummy Javadoc comment.</font><br />
* <font color="#808080">2</font> <font color="#ffffff"> </font>
* <font color="#3f5fbf">* </font><font color="#7f9fbf">@param </font>
* <font color="#3f5fbf">s a String</font><br />
* </code>
*
* @param javaClassContent not null
* @param entity not null
* @return return the original javadoc as String for the current entity
* @throws IOException if any
*/
private static String extractOriginalJavadocContent( final String javaClassContent,
final AbstractJavaEntity entity )
throws IOException
{
if ( entity.getComment() == null )
{
return "";
}
String originalJavadoc = extractOriginalJavadoc( javaClassContent, entity );
int index = originalJavadoc.indexOf( START_JAVADOC );
if ( index != -1 )
{
originalJavadoc = originalJavadoc.substring( index + START_JAVADOC.length() );
}
index = originalJavadoc.indexOf( END_JAVADOC );
if ( index != -1 )
{
originalJavadoc = originalJavadoc.substring( 0, index );
}
if ( originalJavadoc.startsWith( "\r\n" ) )
{
originalJavadoc = originalJavadoc.substring( 2 );
}
else if ( originalJavadoc.startsWith( "\n" ) || originalJavadoc.startsWith( "\r" ) )
{
originalJavadoc = originalJavadoc.substring( 1 );
}
return trimRight( originalJavadoc );
}
/**
* @param content not null
* @return the content without last lines containing javadoc separator (ie <code> * </code>)
* @throws IOException if any
* @see #getJavadocComment(String, AbstractInheritableJavaEntity, DocletTag)
*/
private static String removeLastEmptyJavadocLines( final String content )
throws IOException
{
if ( !content.contains( EOL ) )
{
return content;
}
String[] lines = getLines( content );
if ( lines.length == 1 )
{
return content;
}
List<String> linesList = new LinkedList<String>( Arrays.asList( lines ) );
Collections.reverse( linesList );
for ( Iterator<String> it = linesList.iterator(); it.hasNext(); )
{
String line = it.next();
if ( line.trim().equals( "*" ) )
{
it.remove();
}
else
{
break;
}
}
Collections.reverse( linesList );
return StringUtils.join( linesList.iterator(), EOL );
}
/**
* @param content not null
* @return the javadoc comment with the given indentation
* @throws IOException if any
* @see #getJavadocComment(String, AbstractInheritableJavaEntity, DocletTag)
*/
private static String alignIndentationJavadocLines( final String content, final String indent )
throws IOException
{
StringBuilder sb = new StringBuilder();
for ( String line : getLines( content ) )
{
if ( sb.length() > 0 )
{
sb.append( EOL );
}
if ( !line.trim().startsWith( "*" ) )
{
line = "*" + line;
}
sb.append( indent ).append( " " ).append( trimLeft( line ) );
}
return sb.toString();
}
/**
* Autodetect the indentation of a given line:
* <pre>
* autodetectIndentation( null ) = "";
* autodetectIndentation( "a" ) = "";
* autodetectIndentation( " a" ) = " ";
* autodetectIndentation( "\ta" ) = "\t";
* </pre>
*
* @param line not null
* @return the indentation for the given line.
*/
private static String autodetectIndentation( final String line )
{
if ( StringUtils.isEmpty( line ) )
{
return "";
}
return line.substring( 0, line.indexOf( trimLeft( line ) ) );
}
/**
* @param content not null
* @return an array of all content lines
* @throws IOException if any
*/
private static String[] getLines( final String content )
throws IOException
{
List<String> lines = new LinkedList<String>();
BufferedReader reader = new BufferedReader( new StringReader( content ) );
String line = reader.readLine();
while ( line != null )
{
lines.add( line );
line = reader.readLine();
}
return lines.toArray( new String[lines.size()] );
}
/**
* Trim a given line on the left:
* <pre>
* trimLeft( null ) = "";
* trimLeft( " " ) = "";
* trimLeft( "a" ) = "a";
* trimLeft( " a" ) = "a";
* trimLeft( "\ta" ) = "a";
* trimLeft( " a " ) = "a ";
* </pre>
*
* @param text
* @return the text trimmed on left side or empty if text is null.
*/
private static String trimLeft( final String text )
{
if ( StringUtils.isEmpty( text ) || StringUtils.isEmpty( text.trim() ) )
{
return "";
}
String textTrimmed = text.trim();
return text.substring( text.indexOf( textTrimmed ), text.length() );
}
/**
* Trim a given line on the right:
* <pre>
* trimRight( null ) = "";
* trimRight( " " ) = "";
* trimRight( "a" ) = "a";
* trimRight( "a\t" ) = "a";
* trimRight( " a " ) = " a";
* </pre>
*
* @param text
* @return the text trimmed on tight side or empty if text is null.
*/
private static String trimRight( final String text )
{
if ( StringUtils.isEmpty( text ) || StringUtils.isEmpty( text.trim() ) )
{
return "";
}
String textTrimmed = text.trim();
return text.substring( 0, text.indexOf( textTrimmed ) + textTrimmed.length() );
}
/**
* Workaroung for QDOX-173 about generic.
*
* @param params not null
* @return the wanted params.
*/
private static String[] fixQdox173( String[] params )
{
if ( params == null || params.length == 0 || params.length < 3 )
{
return params;
}
if ( params[0].trim().equals( "<" ) && params[2].trim().equals( ">" ) )
{
String param = params[1];
List<String> l = new ArrayList<String>( Arrays.asList( params ) );
l.set( 1, "<" + param + ">" );
l.remove( 0 );
l.remove( 1 );
return l.toArray( new String[l.size()] );
}
return params;
}
/**
* Wrapper class for the entity's tags.
*/
class JavaEntityTags
{
private final AbstractInheritableJavaEntity entity;
private final boolean isJavaMethod;
/**
* List of tag names.
*/
private List<String> namesTags;
/**
* Map with java parameter as key and original Javadoc lines as values.
*/
private Map<String, String> tagParams;
/**
* Original javadoc lines.
*/
private String tagReturn;
/**
* Map with java throw as key and original Javadoc lines as values.
*/
private Map<String, String> tagThrows;
/**
* Original javadoc lines for unknown tags.
*/
private List<String> unknownsTags;
public JavaEntityTags( AbstractInheritableJavaEntity entity, boolean isJavaMethod )
{
this.entity = entity;
this.isJavaMethod = isJavaMethod;
this.namesTags = new LinkedList<String>();
this.tagParams = new LinkedHashMap<String, String>();
this.tagThrows = new LinkedHashMap<String, String>();
this.unknownsTags = new LinkedList<String>();
}
public List<String> getNamesTags()
{
return namesTags;
}
public String getJavadocReturnTag()
{
return tagReturn;
}
public void setJavadocReturnTag( String s )
{
tagReturn = s;
}
public List<String> getUnknownTags()
{
return unknownsTags;
}
public void putJavadocParamTag( String paramName, String originalJavadocTag )
{
tagParams.put( paramName, originalJavadocTag );
}
public String getJavadocParamTag( String paramName )
{
return getJavadocParamTag( paramName, false );
}
public String getJavadocParamTag( String paramName, boolean nullable )
{
String originalJavadocTag = tagParams.get( paramName );
if ( !nullable && originalJavadocTag == null && getLog().isWarnEnabled() )
{
getLog().warn( getMessage( paramName, "javaEntityTags.tagParams" ) );
}
return originalJavadocTag;
}
public void putJavadocThrowsTag( String paramName, String originalJavadocTag )
{
tagThrows.put( paramName, originalJavadocTag );
}
public String getJavadocThrowsTag( String paramName )
{
return getJavadocThrowsTag( paramName, false );
}
public String getJavadocThrowsTag( String paramName, boolean nullable )
{
String originalJavadocTag = tagThrows.get( paramName );
if ( !nullable && originalJavadocTag == null && getLog().isWarnEnabled() )
{
getLog().warn( getMessage( paramName, "javaEntityTags.tagThrows" ) );
}
return originalJavadocTag;
}
private String getMessage( String paramName, String mapName )
{
StringBuilder msg = new StringBuilder();
msg.append( "No param '" ).append( paramName ).append( "' key found in " ).append( mapName )
.append( " for the entity: " );
if ( isJavaMethod )
{
JavaMethod javaMethod = (JavaMethod) entity;
msg.append( getJavaMethodAsString( javaMethod ) );
}
else
{
JavaClass javaClass = (JavaClass) entity;
msg.append( javaClass.getFullyQualifiedName() );
}
return msg.toString();
}
/**
* {@inheritDoc}
*/
public String toString()
{
StringBuilder sb = new StringBuilder();
sb.append( "namesTags=" ).append( namesTags ).append( "\n" );
sb.append( "tagParams=" ).append( tagParams ).append( "\n" );
sb.append( "tagReturn=" ).append( tagReturn ).append( "\n" );
sb.append( "tagThrows=" ).append( tagThrows ).append( "\n" );
sb.append( "unknownsTags=" ).append( unknownsTags ).append( "\n" );
return sb.toString();
}
}
}